# Adding Authentication
A note about today's structure.
- We'll be adding auth to the Meetup clone in part 1. The react client has been updated, if you're coding along and want to run it alongside the backend as we update it, clone down the [meetup clone client repo](https://github.com/DakotaLMartinez/meetup_clone_client). If you'd like a more detailed discussion of all the changes I made since we last met, I've got [another HackMD](https://hackmd.io/@dlm/phase4-lecture-5-071921) with a full write up that you can check out.
- When we get to the exercise, you can clone down the [reading list client repo](https://github.com/DakotaLMartinez/reading_list_client) to test it out.
- Key Authentication Concepts for the day:
- Sessions
- Cookies
- Password Security
## Backend Support for Auth
The 4 requests we need here to support auth are:
| Endpoint | Purpose | params |
|---|---|---|
| get '/me' | returns the currently logged in user or 401 unauthorized if none exists. Used to determine whether to load the `AuthenticatedApp` or `UnauthenticatedApp` | none |
| post '/login' | returns the newly logged in user | username and password |
| post '/signup' | returns the newly created (and logged in) user | username, password, and password_confirmation |
| delete '/logout' | removes the user_id from the session cookie | none |
## Sessions, Cookies and the Hotel Keycard analogy
- book a reservation -> signup for account
- check in at front desk -> login to account
- key card -> cookie
- card reader -> session
## Client side changes
The overall philosophy here will be to create two components at the top level. One for logged in users and one for non-logged in users. This will allow us to reduce complexity on the frontend by making it clear on the client side when we have a logged in user and when we don't.
Here's what I've done since we last met to support authentication with react:
- Rename the `App` component to `AuthenticatedApp`, remove the Router tag
- Create an `UnauthenticatedApp` with a switch statement to render routes for the Login and Signup components.
- Create a new `App` component that has a useEffect to check if there is a logged in user.
- if there is, render the `AuthenticatedApp` component
- if there isn't render the `UnauthenticatedApp` component
- Add a router tag wrapping the conditional render so both components can redirect.
- Create a `Login` component that will be shown to non-logged in users displaying a Log In form
- Create a `Signup` component that will be shown to non-logged in users displaying a Sign Up form.
- add a Logout link to the `AuthenticatedApp` component so we can login as another user to test things out.
- After we've set up these components, the next step is to go through all of the fetches we're triggering from the `AuthenticatedApp` and its children and add the `credentials: 'include'` option to all `fetch` requests. This will ensure that the cookies issued by our rails api are included in the requests sent back to the server. This is necessary so that the current_user method can read the user_id out of the session cookie using the session method.
### client/src/App.js
```js
import React, { useState, useEffect } from 'react'
import AuthenticatedApp from './AuthenticatedApp'
import UnauthenticatedApp from './UnauthenticatedApp'
import { BrowserRouter as Router } from 'react-router-dom'
function App() {
const [currentUser, setCurrentUser] = useState(null)
const [authChecked, setAuthChecked] = useState(false)
useEffect(() => {
fetch('/me', {
credentials: 'include'
})
.then(res => {
if (res.ok) {
res.json().then((user) => {
setCurrentUser(user)
setAuthChecked(true)
})
} else {
setAuthChecked(true)
}
})
}, [])
if(!authChecked) { return <div></div>}
return (
<Router>
{currentUser ? (
<AuthenticatedApp
setCurrentUser={setCurrentUser}
currentUser={currentUser}
/>
) : (
<UnauthenticatedApp
setCurrentUser={setCurrentUser}
/>
)
}
</Router>
)
}
export default App
```
### client/src/AuthenticatedApp.js
```js
import './App.css';
import GroupsContainer from './components/GroupsContainer'
import EventsContainer from './components/EventsContainer'
import { Switch, Route, Redirect, NavLink } from 'react-router-dom'
function AuthenticatedApp({ currentUser, setCurrentUser }) {
const handleLogout = () => {
fetch(`/logout`, {
method: 'DELETE'
})
.then(res => {
if (res.ok) {
setCurrentUser(null)
}
})
}
return (
<div className="App">
<nav>
<span>
<NavLink to="/groups">Groups</NavLink>{" - "}
<NavLink to="/events">Events</NavLink>
</span>
<span>Logged in as {currentUser.username} <button onClick={handleLogout}>Logout</button></span>
</nav>
<Switch>
<Route path="/groups">
<GroupsContainer />
</Route>
<Route path="/events">
<EventsContainer />
</Route>
<Redirect to="/groups" />
</Switch>
</div>
);
}
export default AuthenticatedApp;
```
### client/src/UnauthenticatedApp.js
```js
import React from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import Login from './components/Login'
import Signup from './components/Signup'
function UnauthenticatedApp({ setCurrentUser }) {
return (
<Switch>
<Route exact path="/">
<Login setCurrentUser={setCurrentUser} />
</Route>
<Route exact path="/signup">
<Signup setCurrentUser={setCurrentUser}/>
</Route>
<Redirect to="/" />
</Switch>
)
}
export default UnauthenticatedApp
```
### client/src/components/Login.js
```js
import React, { useState } from 'react'
import { Redirect, useHistory, Link } from 'react-router-dom'
function Login({ setCurrentUser }) {
const history = useHistory()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (event) => {
event.preventDefault()
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({username, password})
})
.then(res => {
if (res.ok) {
res.json().then(user => {
setCurrentUser(user)
history.push('/groups')
})
} else {
setCurrentUser({ username: "Dakota" })
history.push('/groups')
res.json().then(errors => {
console.error(errors)
})
}
})
}
return (
<div className="authForm">
<Redirect to="/" />
<form onSubmit={handleSubmit}>
<h1>Log In</h1>
<p>
<label
htmlFor="username"
>
Username
</label>
<input
type="text"
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</p>
<p>
<label
htmlFor="password"
>
Password
</label>
<input
type="password"
name=""
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</p>
<p><button type="submit">Log In</button></p>
<p>-- or --</p>
<p><Link to="/signup">Sign Up</Link></p>
</form>
</div>
)
}
export default Login
```
### client/src/components/Signup.js
```js
import React, { useState } from 'react'
import { useHistory, Link } from 'react-router-dom'
function Signup({ setCurrentUser }) {
const history = useHistory()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [passwordConfirmation, setPasswordConfirmation] = useState('')
const handleSubmit = (event) => {
event.preventDefault()
fetch('/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password,
password_confirmation: passwordConfirmation
})
})
.then(res => {
if (res.ok) {
res.json().then(user => {
setCurrentUser(user)
history.push('/groups')
})
} else {
res.json().then(errors => {
console.error(errors)
})
}
})
}
return (
<div className="authForm">
<form onSubmit={handleSubmit}>
<h1>Sign Up</h1>
<p>
<label
htmlFor="username"
>
Username
</label>
<input
type="text"
name="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</p>
<p>
<label
htmlFor="password"
>
Password
</label>
<input
type="password"
name=""
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</p>
<p>
<label
htmlFor="password_confirmation"
>
Password Confirmation
</label>
<input
type="password_confirmation"
name="password_confirmation"
value={passwordConfirmation}
onChange={(e) => setPasswordConfirmation(e.target.value)}
/>
</p>
<p><button type="submit">Sign Up</button></p>
<p>-- or --</p>
<p><Link to="/">Log In</Link></p>
</form>
</div>
)
}
export default Signup
```
## Backend Support for Auth
Again, the 4 requests we need here to support auth are:
| Endpoint | Purpose | params |
|---|---|---|
| get '/me' | returns the currently logged in user or 401 unauthorized if none exists. Used to determine whether to load the `AuthenticatedApp` or `UnauthenticatedApp` | none |
| post '/login' | returns the newly logged in user | username and password |
| post '/signup' | returns the newly created (and logged in) user | username, password, and password_confirmation |
| delete '/logout' | removes the user_id from the session cookie | none |
## Dependencies (Gems/packages)
We need bcrypt so that we can store encrypted (salted and hashed) versions of our users passwords instead of storing passwords in plain text:
```bash
bundle add bcrypt
```
## Configuration (environment variables/other stuff in config folder)
We need to tell rails that we want session cookies. To do that, we'll add the following to the config block in `config/application.rb`
```rb
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
# Use SameSite=Strict for all cookies to help protect against CSRF
config.action_dispatch.cookies_same_site_protection = :strict
```
We'll also need to include the middleware within the `ApplicationController`
```rb
class ApplicationController < ActionController::API
include ActionController::Cookies
# ...
end
```
## Database
We need a `password_digest` column in our `users` table.
```bash
rails g migration AddPasswordDigestToUsers password_digest
```
```bash
rails db:migrate
```
## Models
- We need to add a uniqueness validation for username (and email) So we can consistently find the right user for authentication
- We need to add the `has_secure_password` macro to the model to implement the `authenticate` and `password=` methods used in login & signup actions respectively
## Views/Serializers
- We'll want a `UserSerializer` that returns only the `id`, `username`, and `email`
## Controllers
We'll need actions for:
- `users#show` - for rendering the currently logged in user as json
- `users#create` - for handling the signup form submission and rendering the newly created user as json (while logging them in)
- `sessions#create` - for handling the login form submission and rendering the newly logged in user as json
- `sessions#destroy` - for handling logout and removing the user_id from the session cookie
We'll also need to change the `current_user` method so that it makes use of the user_id stored in the session cookie sent from the browser. This will allow us to login as different users and have our application recognize user's requests by reading the user_id out of the cookie and returning the associated user.
## Routes
```rb
get "/me", to: "users#show"
post "/signup", to: "users#create"
post "/login", to: "sessions#create"
delete "/logout", to: "sessions#destroy"
```