Setting up Password Authentication With B-Crypt
Tags: ruby, rails
Published: 2022-06-29 16:00:40

Phase-4 at FlatIron school brought-forth several new topics including rails, validations, deployment, and authorization. One of the topics I was having trouble with in this phase was password authentication. As such I chose to write my phase-4 blog about the topic. While I had a hard time wrapping my head around this topic, I am writing this to further push my understanding and to give a different point of view, perhaps to someone who is also struggling with the concept. For this example we'll be using rails and react to set up password authentication for users. 


***Cookies***

Before we can begin adding our routes and creating our methods, we need to make sure cookies are enabled in the application configuration file. 

```
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore

#This is for security
config.action_dispatch.cookies_same_site_protection = :strict

```
We will also need to make sure to add _**include ActionController::Cookies**_ in our application controller in order for all controllers to have access to cookies. Cookies are important because it is what will allow us to store the user in a session. 

**Login** 

We'll start with the login route. For a user to log in we will need a route and a post method in the **_SessionsController_**, as we are creating a session for the user that will be logged in.
The route we'll use will also be a post request and go in the routes.rb file. 

```
post "/login", to: "sessions#create"
```
In the **_Sessionscontroller_** we'll add 

```
class SessionsController < ApplicationController
  def create
    user = User.find_by(username: params[:username])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      render json: user, status: :created
    else
      render json: { error: "Invalid username or password" }, status: :unauthorized
    end
  end
end
```

In the code above we are creating a session, where if the password passed in from the params matches the password we have stored in the database, we set the session [:user_id] to the users id. 
The front end will look something like this: 

```
function Login({ onLogin }) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    fetch("/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ username, password }),
    })
      .then((r) => r.json())
      .then((user) => onLogin(user));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
}
```
Above we can see that we are making our post request passing in the username and password. 


**Staying Logged In**

One thing to note is when we refresh the page we lose the value of state which above we have set to _password_ and _username_. Refreshing the page would log the user out and thus would have to log in again. We can handle this by retrieving the user data from the database once the user logs in. For this we would have to create a separate fetch route, that comes from the **_UsersController_** rather than the SessionsController. 

```
get "/me", to: "users#show"
```
and in the "UsersController" we add a find method to find the user associated with said account. 

```
class UsersController < ApplicationController
  def show
    user = User.find_by(id: session[:user_id])
    if user
      render json: user
    else
      render json: { error: "Not authorized" }, status: :unauthorized
    end
  end
end
```
On the front end we can do a fetch request to keep the user logged in. 

```
function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch("/me").then((response) => {
      if (response.ok) {
        response.json().then((user) => setUser(user));
      }
    });
  }, []);
```
**Creating A User** 

To keep things short B-crypt is a gem that allows us to store our passwords in the database in a secure way. Passwords stored as a hash rather than the real value of the password which would be a security risk. When we create a password in a data-table it isn't stored in a password column, but instead in a column named _password_digest_. It is important that this column be named _password_digest_ if we want to use B-crypt. In the user model we'll make sure to add **_has_secure_password_** in order to hash and salt all passwords. 

```
class User < ApplicationRecord
  has_secure_password
end
```

The **_has_secure_password_** we added also provides two instances for our "User" model which are _password_ and _password_confirmation_ This will allow us to create a new user and add password and password-confirmation fields in a signup form. 
*Another bonus is it allows us to raise exceptions to handle any errors. 

Just like before we'll add a create method for a signup form in our front end. 

```
class UsersController < ApplicationController
  def create
    user = User.create(user_params)
    if user.valid?
      render json: user, status: :created
    else
      render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.permit(:username, :password, :password_confirmation)
  end
end
```
The params we are permitting for our create method are the username, password and password-confirmation fields which can be seen below. 

```
function SignUp({ onLogin }) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [passwordConfirmation, setPasswordConfirmation] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    fetch("/signup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        username,
        password,
        password_confirmation: passwordConfirmation,
      }),
    })
      .then((r) => r.json())
      .then(onLogin);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="username">Username:</label>
      <input
        type="text"
        id="username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <label htmlFor="password">Password:</label>
      <input
        type="password"
        id="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <label htmlFor="password_confirmation">Confirm Password:</label>
      <input
        type="password"
        id="password_confirmation"
        value={passwordConfirmation}
        onChange={(e) => setPasswordConfirmation(e.target.value)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
```
**Deleting a Session**
We can now sign in and sign up users, but what if we want to delete the current session or logout? It is very similar, where we'll need a route in our route.rb file, a delete method in our "SessionsController" and a request made from our front end, that will be triggered when we click on a Logout button.

```
#route
delete "/logout", to: "sessions#destroy"

#method in SessionsController
def destroy
  session.delete :user_id
  head :no_content
end

#react/frontEnd request 
function Navbar({ onLogout }) {
  function handleLogout() {
    fetch("/logout", {
      method: "DELETE",
    }).then(() => onLogout());
  }

  return (
    <header>
      <button onClick={handleLogout}>Logout</button>
    </header>
  );
}

```

User authentication for phase-4 was by far one of the most challenging topics for me to comprehend at first, but was still very engaging to learn. As I finish phase-4, I am now able to create full-stack applications. However, there is still much to learn and I am eager to see what else there is out there. 
 

Sources: 
[Accessing A Session ](https://guides.rubyonrails.org/v4.1.4/action_controller_overview.html#accessing-the-session)
[B-Crypt](https://www.usenix.org/legacy/event/usenix99/provos/provos.pdf) 
[Has-Secure Password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html)
FlatIronSchool