Solid Start auth – the secure way (with BCrypt & PSQL)
Tags: solidjs, javascript, webdev, tutorial
Published: 2022-11-28 09:12:11

## What is this about?
This article is about the new meta framework Solid Start and authentication, primarily making it a secure application. I already wrote an article about [how to store data in a PostgreSQL database with Solid Start](https://dev.to/aneshodza/solid-start-with-postgresql-459p). This tutorial builds on top of that knowledge, so if you haven't read that one yet, I advise you to do so.
## Getting started
Note: If the templates have changed in any way, which they probably will, I either advise you to read trough a newer tutorial or start with [this](https://github.com/aneshodza/solid-start-auth-made-secure/tree/90298c5dce724db975acd6a0dd70821a0665e8e5) code. If the templates stayed the same, continue with the setup.
First, we need to create a folder in which the setup will happen. We can do that with `mkdir secure-auth-with-solid-start` and then directly go into that folder with `cd secure-auth-with-solid-start`. To initialize our project we type `npm init solid`, which brings us into the setup wizard. To have a foundation we pick the "with auth" template:
```bash
✔ Which template do you want to use? › with-auth
✔ Server Side Rendering? … yes
✔ Use TypeScript? … yes
```
Next we have to install our packages by typing `yarn`. You can also use `npm install`, but I got used to yarn.
## Writing the application
In this part we will actually make our application and secure it properly. I already have an article about how passwords are secured properly: ["how (not) to store passwords"](https://dev.to/aneshodza/how-not-to-store-passwords-20ib)
### Let's hash! (Next chapter uses BCrypt)
Important: This chapter does it by hand so the reader gets a better understanding of what is happening. If you are here for the BCrypt part, please skip this chapter. You can obviously also read trough both.
To start with our hashing we need to find the functions responsible for logging in and registering. We can find that in the file, that also exports our database (`src/db/index.ts`).
First, we will go over the create function:
```typescript
async create({ data }) {
  let user = { ...data, id: users.length };
  users.push(user);
  return user;
},
```
To check what data we exactly have when a user registers, we can `console.log(user)`:
![Screenshot of console displaying that the server returns us a username and a password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy7ryxbv585yhff1mggc.png)
Note, that you need to restart the server to see these changes.
What else do we need? Well, we also need a salt. This is one possible way to do it:
```typescript
async create({ data }) {
  let salt = Math.random().toString(32).slice(2);
  //...
}
```
Next we need to digest. Unlike in ruby, javascript does not have a native Sha256 function, which is why we will make use of [crypto-js](https://www.npmjs.com/package/crypto-js). I added it with `yarn add crypto-js`.
Taking a quick look into the documentation of crypto-js shows how easy it actually is to do:
```typescript
import sha256 from 'crypto-js/sha256';

const hashDigest = sha256(nonce + message);
```
We will implement exactly this code into our `/db/index.ts` file:
```typescript
import sha256 from "crypto-js/sha256";

export const db = {
  user: {
    async create({ data }) {
      //...
      const digestedPassword = sha256(data.password + salt)
                                  .toString();
      console.log('our digested password:', encryptedPassword);
      //...
    },
    //...
  },
};
```
And this should yield us a very long and random looking string when we register a new user:
![Screenshot of a console log showing a digested password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hk5mfll5jn7qcxz2qyux.png)
Finally we need modify the user we will save and print it below:
```typescript
let user = {
  username: data.username,
  id: users.length,
  salt: salt,
  digested_password: digestedPassword
}
console.log(user)
```
Now on the next line (`users.push(user)`) we should get a typescript error. That is because our initial user has different attributes, so don't get scared. We will fix that later.
One last thing that we are still lacking is a pepper, so we will create that one. I just made a `export const` above the `let users` initialization of the array:
```typescript
export const pepper: string = "make_sure_the_pepper_is_long"
                        + "_and_secure_so_that_it_is"
                        + "_hard_to_guess";
let users = [{ id: 0, username: "kody", password: "twixrox" }];
```
We export it, because we will need this pepper in another file and we don't want to hard code.
Finally also add to the `sha256` algorithm:
```typescript
const digestedPassword = sha256(data.password + salt + pepper)
                                  .toString();
```
Next, we will change the login function to match the system we have with the register. The login is located in `/src/db/session.ts`, which is why we exported the pepper. Now if we go into `session.ts` we see on line three:
```typescript
import { db } from ".";
```
Which we change to also import the `pepper` and don't forget to import the Sha265 algorithm on the next line:
```typescript
import { db, pepper } from ".";
import sha256 from "crypto-js/sha256";
```
Finally we can rewrite our login function. What it does is digest the user input and then match that up with the password in our database, so that we never have to get the plain text password back (on line 15 of `session.ts`):
```typescript
export async function login({ username, password }: LoginForm) {
  const user = await db.user.findUnique({ where: { username } });
  if (!user) return null;
  const digestedInput = sha256(password
                              + user.salt
                              + pepper)
                                .toString();
  const isCorrectPassword = digestedInput === user.digested_password;
  if (!isCorrectPassword) return null;
  return user;
}
```
Here you will see another TypeScript error, but we will clear those later. Now we can create a new user with the password "123456" and check the logs for his attributes (make sure to restart the server):
![Screenshot of logs displaying a user with a salt and an encrypted password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/elgsohb1wyr1vj1bvel2.png)
We can copy those attributes to create a new base user, which works with our digestion. For that we change the `users` array in `/src/db/index.ts/`:
```typescript
let users = [{
  id: 0,
  username: "@aneshodza",
  salt: "qm9616pd3eg",
  digested_password: "a5c594cb0938b5d118f0c4d0e4fbf4a64838c2390da1334a85cef73955008fd1"
}]
```
Now if we restart the server and try to log into our base user we should quickly see:
![Screenshot of a logged in user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnksff64ksz7mzoqpyzh.png)
It works!
Next, we want to do this with BCrypt, but if you want to have the source code to this you can get it [here](https://github.com/aneshodza/solid-start-auth-made-secure-no-bcrypt)
### Now with BCrypt
We want to bring our website to industry standards, which is why we will use [BCrypt](https://www.npmjs.com/package/bcrypt), the probably most used library for encryption and digestion.
Step one is to go trough the "setting up" chapter again, because we will do this in a separate application.
Now we want to get to using BCrypt. If you didn't read the first part: The register function is in `/src/db/index.ts` and the login is in `src/db/session.ts`.
Let us start off by installing the package. I use yarn so I install it with `yarn add bcrypt`.
We will work in parallel with the documentation, so I advise you to keep [this](https://www.npmjs.com/package/bcrypt) open on another tab.
We start off in the `/src/db/index.ts` file, where the register is located. Step one is to `require` the BCrypt object and set a `const` for the salt rounds:
```typescript
import bcrypt from 'bcrypt';
const saltRounds = 10;
```
Salt rounds are the "cost factor" of the digestion.  That tells us how often the string gets digested, so the bigger this number, the harder it is to brute force but also the even create the hashes. Having 10 rounds should definitely be sufficient.
Next, we want to create the user with a digested password. BCrypt already offers us a method for that, which we will use at the top of our create function:
```typescript
    create({ data }) {
      const user = {
        id: users.length,
        username: data.username,
        digested_password: bcrypt.hashSync(data.password, saltRounds),
      };
      console.log('user', user);
      users.push(user);
      return user;
    },
```
You should see a typescript error. To fix that just delete the first user that the application gave us. 
Now we are still missing a pepper. With BCrypt it is not really necessary, but to keep good practice we will still use it. For that we just create a `const` and append it to our initial string:
```typescript
export const pepper:string = "this_is_some_really_secure_pepper";
export const db = {
  //...
  async create({ data }) {
    const user = {
      //...
      digested_password: bcrypt.hashSync(data.password + pepper, saltRounds),
    }
    //... 
  },
  //...
};
```
We export the pepper because we will need it in another file later.
Now if you log the created user you will see a digested password:
![Image of our newly created user with a digested password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8n6a7gkvbheude4ledk.png)
Having secured it with an additional pepper, we can finally do the login. For that we go into our `login` function in `/src/db/session.ts`. First we import bcrypt at the top just like we did in the other file, so we can just use a preset BCrypt function to do it for us:
```typescript
export async function login({ username, password }: LoginForm) {
  const user = await db.user.findUnique({ where: { username } });
  if (!user) return null;
  let result = bcrypt.compareSync(password + pepper, user.digested_password);
  if (!result) return null;
  return user;
}
```
Now you should still see an error, because we didn't import pepper yet. Where you imported `db`, you just add `pepper` next to it and it should work.
### Where is our salt?
BCrypt has its own system for salting, where they have the salt including the salt rounds encoded in one string. Next to that they also encode a lot of other information in it, so that the complex library works off of a single string:
```
$2y$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |                     |
 |  |  |                     hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |
 |  |  salt = nOUIs5kJ7naTuTFkBy1veu
 |  |
 |  cost-factor = 10 = 2^10 iterations
 |
 hash-algorithm = 2y = BCrypt
```
### Fixing vite issues
If you check your console you should see a lot of red:
![Vite error messages caused by the BCrypt library](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmb9vqtn3zlafa4odu0f.png)
We can fix that by going into our `/vite.config.js` and telling it to not optimize this library:
```typescript
import solid from "solid-start/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [solid()],
  optimizeDeps: {
    exclude: ['bcrypt']
  }
});
```
Now your console should be error-free and the code should run:
![Shows user @aneshodza being logged in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kow1659wxmzoxedsl2ks.png)
## Creating the database connection
This is the part in which you should have read [my first article](https://dev.to/aneshodza/solid-start-with-postgresql-459p). There I explain how this works in depth.
### Setting up
We will use the [PostgreSQL npm package](https://www.npmjs.com/package/postgres). Because I use yarn I will install it with `yarn add postgres`. If you use any other package manager, use the command that is needed there.
### Creating the schema
Now we need to create our database and the table. We will first connect to the postgres-cli with `psql -U <username> -d postgres`, which will throw us into the postgres database. There we create a database and a 'application_user' table with the before used attributes:
```sql
postgres=# CREATE DATABASE solid_start_auth_made_secure;
CREATE DATABASE

postgres=# \c solid_start_auth_made_secure
You are now connected to database "solid_start_auth_made_secure".

solid_start_auth_made_secure=# CREATE TABLE application_users (
  id serial primary key,
  username varchar(255),
  digested_password varchar(255)
);
CREATE TABLE
```
Now if we print everything on the table we should see following:
```sql
solid_start_auth_made_secure=# SELECT * FROM application_users;

 id | username | digested_password
----+----------+-------------------

(0 rows)
```
### Connecting a client to our database
Next, we have to connect our database to the backend. We will use the same library we did in ["Solid Start with PostgreSQL"](https://dev.to/aneshodza/solid-start-with-postgresql-459p), so we have to install it again with `yarn add postgres`
Then we go into `src/db/index.ts`, where we create an object which contains our database connection at the top of the file:
```typescript
import postgres from "postgres";

const sql = postgres({
  host: "localhost",
  port: 5432,
  database: "solid_start_auth_made_secure",
  username: "<USERNAME>"
});
```
Now you should see some `Big integer literals` error in your terminal. That error is caused by vite. We fix it by telling the `vite.config.ts` file to not optimize this:
```typescript
import solid from "solid-start/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [solid()],
  optimizeDeps: {
    exclude: ['bcrypt', 'postgres'],
  }
});
```
Restart the server and voilà: The error is gone.
### Querying the database
First, we store the users in our database. For that we go into `/src/db/index.ts` where we use the npm library to change it to the user being stored in the database:
```typescript
async create({ data }) {
  return await sql`INSERT INTO application_users (username, digested_password) 
      VALUES (${data.username}, ${bcrypt.hashSync(data.password + pepper, saltRounds).toString()})
      RETURNING *;`;
},
```
Now if we register a user and query our database:
```
solid_start_auth_made_secure=# SELECT * FROM application_users;

 id |  username  |                      digested_password
----+------------+--------------------------------------------------------------
  4 | @aneshodza | $2b$10$.IyUhM832d24cD.uWuQrUubuQLkGQWw76Ot5C/r0XGniS666L0hvO

(1 row)
```
Now we also need to rewrite our `findUnique` function inside of the same file so it searches the db for users:
```typescript
async findUnique({ where: { username = undefined, id = undefined } }) {
  if (id !== undefined && id.toString() !== 'NaN') {
    // return users.find((user) => user.id === id);
    const result = await sql`SELECT * FROM application_users WHERE id = ${id};`;
    return result.at(0);
  } else if (username !== undefined) {
    // return users.find((user) => user.username === username);
    const result = await sql`SELECT * FROM application_users WHERE username = ${username} LIMIT 1;`;
    return result.at(0);
  }
  return null;
},
```
This will return a user. Now if we try to log into the application:
![Image of logged in user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9fszl18d0x8iru4merqs.png)
It works!
If you want the source code to this approach, [here you go](https://github.com/aneshodza/solid-start-auth-made-secure)
## Conclusion
I will say the same as I did in my first tutorial. While solid start had a solid start ;), it is still in very early development and there even the team of solid start agrees: Don't use it in production software yet. I think with more native support of e.g. databases we could do a lot more than now. Happy hacking :)