# 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 (`project-management-server` and `project-management-client`) and incorporate the users into them. ## Backend Set-Up Let's start from backend/server side. Remember when you cloned `project-management-server` that there was couple 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 `username` and `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 ``` 2. Un-comment out the lines *17* through *20* ```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: app.use(session({ secret:"some secret goes here", resave: true, saveUninitialized: true })); ... ``` We use `express-session` npm package to maintain the sessions and our users in it. 5. We installed `passport` but we still didn't allow our app to use it. ```js // app.js ... // USE passport.initialize() and passport.session() HERE: app.use(passport.initialize()); app.use(passport.session()); ... ``` We have been through 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 be inside `routes/auth-routes.js`. Let's start with creating the users, the `signup` route. ::: Side note: We will not have any get routes since we are not rendering any pages on the server side. Our forms will be rendered on client/React side. Also, the same as in other backend routes, we will use `.json()` to send/retrieve data to/from database. Let's demonstrate: ```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 purposes.' }); 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 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; ``` ::: Side note: Notice 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 `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 user. And yes, we can successfully create new user, so let's proceed to `login` route. ```js // 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 `local strategy` which is being used in `configs/passport.js`. At this point, we can test the route. Works perfectly! Amazing. Let's finish up our backend with `logout` route but we will also introduce `loggedin` route, which will be our helper in checking do we have user in the session and who he is. ```js // 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!' }); }); authRoutes.get('/loggedin', (req, res, next) => { // req.isAuthenticated() is defined by passport if (req.isAuthenticated()) { res.status(200).json(req.user); return; } res.status(403).json({ message: 'Unauthorized' }); }); module.exports = authRoutes; ``` Let's test all the `authRoutes` before we proceed. We can see that if we are logged in our `/loggedin` route returns the user object. However, if we are not logged in and we try to access to `/loggedin` we will get this response: **{"message": "Unauthorized"}**. It works great! :heart_eyes:. 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 the owner property to it. ```js // 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 *POST* route to create new project: ```js //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 <== !!! }) .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: Toward the end of the lesson where we created our backend, we set up `cors` to allow cross-origin communication. In case you missed that, the steps to take are: - install `cors` npm package: ```bash $ npm install --save cors ``` - import it at the beginning of `app.js` with all the other imports and use it toward the end of the file, just before `the routes`, where you see: *// ADD CORS SETTINGS HERE TO ALLOW CROSS-ORIGIN INTERACTION*: ```javascript // app.js ... const cors = require('cors'); ... app.use(cors({ credentials: true, origin: ['http://localhost:3000'] })); // ROUTES MIDDLEWARE STARTS HERE: ... ``` With this, our backend is complete. ## Frontend Set-Up As we said at the beginning of this learning unit, we will be using our `project-management-client` app. ::: Side note: Remember, the client side is running on port `3000` and server is on `5000`. Eventually, down the road, we will have components for *signup* and *login* and each component can have `axios` call to 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 (we can call it **`service`**). *Services* are like the *bridges* between the two app - server and client. They allow cross-origin communication and exchange the data. ### Auth Service Inside `components` folder, create `auth` folder and add `auth-service.js` file into it. In this file, first, we need to create an `AuthService` class, that will contain all the methods and also we need to use the `create` method of **axios** to build a new instance. So far, we have this code in `components/auth/auth-service.js`: ```js // auth/auth-service.js import axios from 'axios'; class AuthService { constructor() { let service = axios.create({ baseURL: 'http://localhost:5000/api', withCredentials: true }); this.service = service; } } export default AuthService; ``` The `withCredentials` indicates whether or not cross-site Access-Control requests should be made using credentials, so there is the *magic*. #### Adding the methods After finishing the `constructor`, we can start adding methods, so let's start with the `signup`: ```js // auth/auth-service.js ... signup = (username, password) => { return this.service.post('/signup', {username, password}) .then(response => response.data) } ... ``` We will receive the `username` and `password` from the component and request the `http://localhost:5000/api/signup` URL. The second argument inside this `post` request is the data we are sending to server (in our case that is *username* and *password*) but also don't forget that we set up in our constructor that every `axios` call will be `withCredentials`, so that is actually the third argument that we are sending to backend inside this `post` route. When receiving the response, we will pass it to the component. ### `Signup` Component Now we need to create `<Signup />` component. Inside `auth` folder add `Signup.js` file. Our component will have a `form`, and when the user clicks on the `Signup` button, it will call to the method we created on the `AuthService` file. First, let's set up our `Signup` component: ```jsx // auth/Signup.js import React, { Component } from 'react'; import AuthService from './auth-service'; class Signup extends Component { constructor(props){ super(props); this.state = { username: '', password: '' }; this.service = new AuthService(); } // handleChange() and handleSubmit() will be added here render(){ return( // more code will be added here ) } } export default Signup; ``` As you can see, we created `this.service` as an instance of `AuthService` that we previously imported from our `auth-service.js` file. This will allow us to access to all the methods available in that file ( for now, it is only `signup()` method but soon we will add more ). 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; this.service.signup(username, password) .then( response => { this.setState({ username: "", password: "", }); // this.props.getUser(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> ) } ... ``` ::: Side note: `this.props.getUser(response)` is commented out but not for too long. Let's make some changes in `App.js`. 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 />`: ```js // 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 been created in the database, so connection is established between our two apps. However, we can't keep track of our user. If we just refresh the page, we are already logged out. Also if we change page, we lose our user. The way to go about this is to introduce the state on the global level, or as we learned, to lift the state of user up to `App.js`. This means we will have to add *constructor()* to `App.js` and make some more changes. ```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 }; } getTheUser= (userObj) => { this.setState({ loggedInUser: userObj }) } render() { return ( <div className="App"> <Navbar /> <Switch> <Route exact path='/signup' render={() => <Signup getUser={this.getTheUser}/>}/> <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 out** that line: **`this.props.getUser(response)`** and let's make this a bit more clear. First, let's explain why we changed `<Route />` that holds `<Signup />` component: by using `render` we allow passing props down to component, in our case, down to `Signup` component. Why we are passing prop? Well, the line that we just un-commented out in `Signup` component 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 out was to remodel the way we rendered `Signup` component by adding the `render` and just replicating the usual way we pass the prop (*<Signup getUser={this.getTheUser}/>*). Now when we have `user` object in `App.js`, we actually use it when updating the state in `getTheUser()` and set our `loggedInUser` to the passed `user` from `Signup` component. Now our `App.js` knows when we have user in session. :::info: We can use `loggedInUser` variable name because, if you remember, we set up our backend so that our users are automatically logged in after they signup. When we have user available in `App.js` it is very easy to pass it down to any other component. Let's now demonstrate this with `<Navbar />` component. Our `<Navbar />` is actually just functional (stateless) component for now. Let's turn it to *class* (stateful) component. ```jsx // navbar/Navbar.js import React, { Component } from 'react'; import { Link } from 'react-router-dom'; class Navbar extends Component { constructor(props){ super(props); this.state = { loggedInUser: null }; } // more code here } export default Navbar; ``` Okay, we see that in our `state` we have `loggedInUser` property which is set to `null`. Led by logic from earlier, we see that if we pass user object from `App.js` to `<Navbar />`, we can set state to change value of *loggedInUser* to the passed user object. And that is exactly what we are going to do. Let's pass the object down to `<Navbar />` in our `App.js` and then we will explain how to set the state in that component that receives props. ```jsx // App.js ... <Navbar userInSession={this.state.loggedInUser} /> ... ``` `userInSession` is prop that's being passed to `Navbar` so let's see how we can update the state in that component: ```jsx // navbar/Navbar.js ... class Navbar extends Component { ... componentWillReceiveProps(nextProps) { this.setState({...this.state, loggedInUser: nextProps["userInSession"]}) } render(){ if(this.state.loggedInUser){ return( <nav className="nav-style"> <ul> <li>Welcome, {this.state.loggedInUser.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 has to be updated. If the props are tied to the state of the component, a change in it will mean a change in the state of the component. **componentWillReceiveProps** is a method that is called before a component does anything with the new props. This method is called with the new props passed as an argument. Here, we have access to the next set of props and the present ones. Therefore, using this method, we can compare the present props with the new ones and check if anything has really changed. As we just said, **something needs to change in the parent component in order to trigger update in child component**. To see the change in `<Navbar />` component, we will now get its parent component (`App.js`) to update its state in order to pass the updated props to `<Navbar />`. To prepare everything for success, first we will add a new method to `auth-service.js`: ```js // auth/auth-service.js ... loggedin = () => { return this.service.get('/loggedin') .then(response => response.data) } ... ``` And our *App.js* now will look like this: ```jsx // App.js ... import AuthService from './components/auth/auth-service'; class App extends Component { constructor(props){ super(props) this.state = { loggedInUser: null }; this.service = new AuthService(); } fetchUser(){ if( this.state.loggedInUser === null ){ this.service.loggedin() .then(response =>{ this.setState({ loggedInUser: response }) }) .catch( err =>{ this.setState({ loggedInUser: false }) }) } } getTheUser= (userObj) => { this.setState({ loggedInUser: userObj }) } render() { this.fetchUser() if(this.state.loggedInUser){ return ( <div className="App"> <Navbar userInSession={this.state.loggedInUser} /> <Switch> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> </div> ); } else { return ( <div className="App"> <Navbar userInSession={this.state.loggedInUser} /> <Switch> <Route exact path='/signup' render={() => <Signup getUser={this.getTheUser}/>}/> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> </div> ); } } } export default App; ``` Let's test! Looks like we are React developers now! :relieved: Let's finish up authentication by adding `login` and `logout` functionalities. In our `auth-service.js` add following methods: ```js // auth/auth-service.js login = (username, password) => { return this.service.post('/login', {username, password}) .then(response => response.data) } logout = () => { return this.service.post('/logout', {}) .then(response => response.data) } ``` Also let's add `<Login />` component in `auth` folder: ```jsx // auth/Login.js import React, { Component } from 'react'; import AuthService from './auth-service'; import { Link } from 'react-router-dom'; class Login extends Component { constructor(props){ super(props); this.state = { username: '', password: '' }; this.service = new AuthService(); } handleFormSubmit = (event) => { event.preventDefault(); const username = this.state.username; const password = this.state.password; this.service.login(username, password) .then( response => { this.setState({ username: "", password: "" }); this.props.getUser(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 make update in `<Navbar />` related to both, `login` and `logout` so our `<Navbar />` component will now look like this: ```jsx // navbar/Navbar.js import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import AuthService from '../auth/auth-service'; class Navbar extends Component { constructor(props){ super(props); this.state = { loggedInUser: null }; this.service = new AuthService(); } componentWillReceiveProps(nextProps) { this.setState({...this.state, loggedInUser: nextProps["userInSession"]}); } logoutUser = () =>{ this.service.logout() .then(() => { this.setState({ loggedInUser: null }); this.props.getUser(null); }) } render(){ if(this.state.loggedInUser){ return( <nav className="nav-style"> <ul> <li>Welcome, {this.state.loggedInUser.username}</li> <li><Link to='/projects' style={{ textDecoration: 'none' }}>Projects</Link></li> <li> <Link to='/'> <button onClick={() => this.logoutUser()}>Logout</button> </Link> </li> </ul> </nav> ) } else { return ( <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> ) } } } export default Navbar; ``` And finally, let's update `App.js`: ```jsx // App.js ... import Login from './components/auth/Login'; ... <Switch> <Route exact path='/signup' render={() => <Signup getUser={this.getTheUser}/>}/> <Route exact path='/' render={() => <Login getUser={this.getTheUser}/>}/> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> ... ``` Authentication part works great BUT don't forget that our projects will have the owner and cross-origin communication the way we set it up in `cors` needs credentials to be passed as well and currently we don't pass user's information back to server. Also, in our backend, we set up that the owner is actually `ObjectId` of currently logged in user. So let's add `{withCredentials:true}` to all our `axios` calls in all the components in `projects` folder: ```js // projects/AddProject.js ... axios.post("http://localhost:5000/api/projects", { title, description }, {withCredentials:true}) ... // projects/EditProject.js ... axios.put(`http://localhost:5000/api/projects/${this.props.theProject._id}`, { title, description }, {withCredentials:true}) ... // projects/ProjectDetails.js ... // getSingleProject() axios.get(`http://localhost:5000/api/projects/${params.id}`, {withCredentials:true}) // deleteProject() axios.delete(`http://localhost:5000/api/projects/${params.id}`, {withCredentials:true}) ... // projects/ProjectList.js ... axios.get(`http://localhost:5000/api/projects`, {withCredentials:true}) ... ``` Pretty much we have everything we need in our app. But we want our apps to be perfect and to protect our users and their projects. :muscle: Let's update `<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: . ```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 project that was created by some other user, we can't see `edit` and `delete` options. :bulb: **Advanced topic**: **Protected routes** Our projects are safe, unless our logged in user created project, they can't edit or delete it. But we don't want anyone to have access to `/projects` or `/projects/:id` unless they are logged in. Let's protect our routes. First, let's create `protected-route.js` in `auth` folder and let's add this code into that file: ```js // auth/protected-route.js import React from 'react'; import { Route, Redirect } from 'react-router-dom'; const protectedRoute = ({component: Component, user, ...rest}) => { console.log({component: Component, user, ...rest}) return ( <Route {...rest} render={ props => { if(user){ return <Component {...props} loggedInUser={user}/> } else { return <Redirect to={{pathname: '/', state: {from: props.location}}} /> } } } /> ) } export default protectedRoute; ``` Believe it or not, we are on the last activity in this learning unit! :satisfied: In `App.js` import `protected-route.js` as a component and replace regular `<Route />` with new one `<ProtectedRoute />`. ```jsx // App.js ... import ProtectedRoute from './components/auth/protected-route'; ... render() { {this.fetchUser()} if(this.state.loggedInUser){ return ( <div className="App"> <Navbar userInSession={this.state.loggedInUser} getUser={this.getTheUser} /> <Switch> <ProtectedRoute user={this.state.loggedInUser} path='/projects/:id' component={ProjectDetails} /> <ProtectedRoute user={this.state.loggedInUser} path='/projects' component={ProjectList} /> </Switch> </div> ); } else { return ( <div className="App"> <Navbar userInSession={this.state.loggedInUser} getUser={this.getTheUser} /> <Switch> <Route exact path='/signup' render={() => <Signup getUser={this.getTheUser}/>}/> <Route exact path='/' render={() => <Login getUser={this.getTheUser}/>}/> <ProtectedRoute user={this.state.loggedInUser} path='/projects/:id' component={ProjectDetails} /> <ProtectedRoute user={this.state.loggedInUser} path='/projects' component={ProjectList} /> </Switch> </div> ); } } ... ``` 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. We used `cors` npm package to send user's credentials between two apps. Also, we lifted the state of `loggedInUser` so each component can keep track of a status of currently logged in user and get notified when user logs out. ## Extra Resources - [React Forms](https://reactjs.org/docs/forms.html) - [Axios - withCredentials](https://github.com/axios/axios)