Building Secure Neon-Infused Web Apps with Auth0, Express, and EJS
Tags: codenewbie, database, node, postgres
Published: 2023-12-18 08:00:00

Data storage management is required for modern web applications to operate optimally and scale automatically. Without data storage management, your current session on social media apps won't remember or display your most recent messages on the platform.

This service is needed to prevent data leakage and unauthorized access to user information. It is important to note that the setup and configuration of **Auth0** will act as a secure authentication system.

Neon is a cloud-native serverless Postgres database that lets you quickly spin up a database instance and connect with it. It has features like autoscaling, branching, and bottomless storage, with its architecture supporting separate computing and storage.

This article examines how to set up and leverage the capabilities of Auth0 with a Postgres database like Neon for any development workflow that needs storage and user authentication.


## Prerequisites

You should consider the following to complete the tutorial:

- Install [Node.js](https://nodejs.org/en/) >= 20 on your local machine  
- Familiarity with using [Embedded JavaScript](https://ejs.co/) (EJS)
- An [Auth0](https://auth0.com/signup?place=header&type=button&text=sign%20up) account for creating an application with authentication

Check out the [source code](https://github.com/Terieyenike/neondb-with-auth0) and the [live app](https://neondb.onrender.com/) for a complete review of this build.


## Understanding Auth0

Auth0 is a cloud-based user authentication, authorization, management, and security platform that serves as the gateway access to your application. 

It enables developers to add secure authentication and authorization flows to applications (web and mobile) with tools and services that include features like universal login, multifactor authentication (MFA), and more.


## Connecting to a Neon Database

Since Neon is a cloud-based service, you need an account. [Sign up for a free tier now](https://console.neon.tech/sign_in)!

Once you log in as a new user, Neon prompts you to create a new project. Fill in the required parameters and click the **Create project** button to create a database.


![setup screen](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701347155366_neon.jpg)


After creating the database, the dashboard shows the **connection string** with its details, as seen below; select the database and the type of connection.


> Note: In Neon, databases are stored on branches. By default, a project has one branch and one database.
> You can select the branch and database from the available drop-down menus.


![connection string](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701358966476_Screenshot+2023-11-30+at+16.41.02.jpg)



Let’s create a database **table** from the Neon console. Navigate to the menu's left pane and click on the **SQL Editor**. The function of the **SQL Editor** is to run queries on your Neon databases.

The following SQL creates a table called **student** and inserts some values into it:

```sql
    CREATE TABLE student (
    student_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    first_name VARCHAR(255) NOT NULL,
    last_name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    );
    
    INSERT INTO student (first_name, last_name, email)
    VALUES ('Teri', 'Eyenike', 'oteri@iamteri.tech'),
    ('Dubem', 'Ochuaka', 'chidumebi@gmail.com'),
    ('Kola', 'Odugbesan', 'kolapo@gmail.com'),
    ('Gojep', 'Kwachu', 'gojep@yahoo.com'),
    ('Chinasa', 'Muoneke', 'chinasa@hotmail.com');
```

Running the above **CREATE** command may raise an error if the **UUID** extension, which generates unique identifiers for primary keys, is not installed.

Run this command below, and afterward, rerun the **create** and **insert** command:

```sql
    CREATE extension IF NOT EXISTS "uuid-ossp"
```

In the **CREATE** and **INSERT** codes above, the following is happening:

- Create: The **Create** command creates a new table called student to store the student information, and within it are the defined [data types](https://www.postgresql.org/docs/current/datatype.html) for each domain/constraint
- Insert: The **Insert** command adds the data into the **student** table, specifying each attribute or field accordingly

Remember to query the student table with this command: 

```sql
    SELECT * FROM student;
```

Click on the **Tables** menus to see the data arranged in rows and columns as shown below:


![student table](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701708420567_table.jpg)



## Connecting Postgres Locally

There are two ways to connect with the Postgres database which can be in the terminal or using a Postgres GUI client app like DBeaver. But first, download the PostgreSQL installer for [macOS](https://postgresapp.com/) or [Windows](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads), depending on your OS. The setup and installation come with the `psql` command, a tool shipped with Postgres that allows you to communicate with Postgres through the command line.


> Remember to add Postgres to your PATH variable by following the instructions [in the documentation](https://postgresapp.com/documentation/cli-tools.html).

Head over to your Neon console dashboard and copy the connection string details. Paste the string in your terminal to connect to your Neon Postgres database. Here’s an example:

```sql
    psql 'postgresql://Terieyenike:Gxxx@ep-odd-butterfly-33244281.eu-central-1.aws.neon.tech/neondb?sslmode=require'
```

To query and select the entire **student** table in the terminal, run the same command as in the **SQL editor** in your dashboard:

```sql
    select * from student;
```

You should see something like this:

![query result from student table](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701708550790_Screenshot+2023-12-04+at+17.48.27.jpg)

> You can repeat the same process for inserting user data into the **student** table in neon using the `INSERT INTO` command in the terminal

## Setting up Auth0

Log in to your admin Auth0 dashboard to create a new application. An application in Auth0 can be a native app that executes on a mobile device, a single-page web app that runs on a browser, or a regular web application that executes on a web server. The latter is chosen for development using the [Express framework](https://expressjs.com/) in this tutorial. 

To create a new application, navigate to the **Applications** menu and select **Applications** from the dropdown. 


![creating applications in auth0](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701978057938_Screenshot+2023-12-07+at+19.34.39.jpg)


After that, click the **+ Create Application** button which opens a dialog box to choose an application type. Make sure you name your application and select the **Regular Web Applications**.


![regualar web applications](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701978875375_Screenshot+2023-12-07+at+20.52.39.jpg)


The most crucial step of this setup is choosing a technology for the project. Select **Node.js (Express).**


![web technology](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701979213744_Screenshot+2023-12-07+at+20.59.18.jpg)



## Installation and setup

Make a new directory in the terminal by running:

```bash
    mkdir neon-nodejs
```

Navigate into the directory and initialized it with the command:


    cd neon-nodejs
    
    npm init -y

The `npm init` with the flag `-y` accepts all the defaults and creates the `package.json` file which contains metadata for the application and the dependencies for the app's functioning.

Install these dependencies:


    npm install dotenv ejs express express-openid-connect pg

The command above takes care of the following:


- `dotenv`: loads environment variables (key-value pairs) from the `.env` file, keeping them secrets
- `ejs`: a templating language that generates HTML markup with plain JavaScript in Node.js
- `express`: backend framework for creating and building APIs
- `express-openid-connect`: It provides an identity layer to verify the identity of users 
- `pg`: the package for interfacing with the PostgreSQL database and connecting to the Neon database

Also, install `nodemon`, which will listen to changes and restart the server:


    npm install --save-dev nodemon

 
 Update and replace the code to the `package.json` in the scripts section:
 
```js
    {
      "scripts": {
        "start": "node app.js",
        "dev": "nodemon app.js"
      },
    }
```
 
Create the application entry point with the command which creates a new file in the directory, `neon-nodejs`: 


    touch app.js

To run the application, use:

```fish
    npm run dev
```

## Integrating Auth0 in Node.js

Click the **Integrate Now** button, and the following screen accepts the defaults to configure Auth0. 


> You can change it if you are using a different port number from **3000**. In your entry app file, `app.js`, the server will function on port **3000**.


![integration](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701983244663_integrate.jpeg)


**Creating Environment Variables**
After clicking on the **Save Settings And Continue** button from above, on the next screen under the Configure Router, replace the values for secret, baseURL, clientID, and issuerBaseURL and replace them with yours in the `.env` file:


    DATABASE_URL="Replace with your database url connection string in NeonDB"
    SECRET="generate a secret key with the command openssl rand -hex 32"
    BASE_URL=http://localhost:3000
    CLIENT_ID="Replace with your Auth0 client id"
    ISSUER_BASE_URL="Replace with your Auth0 issuer base url"



> Note: To generate a **SECRET** key, run this command  `openssl rand -hex 32` in your terminal and replace its value as given

Let’s jump in with creating the server.

In the entry file, add this code block:

```js
    // app.js

    const path = require('path');
    const express = require('express');
    const { auth } = require('express-openid-connect');
    const { requiresAuth } = require('express-openid-connect');
    const { Pool } = require('pg');
    
    const app = express();
    
    require('dotenv').config();
    
    const config = {
      authRequired: false,
      auth0Logout: true,
      secret: process.env.SECRET,
      baseURL: process.env.BASE_URL,
      clientID: process.env.CLIENT_ID,
      issuerBaseURL: process.env.ISSUER_BASE_URL,
    };
    
    app.set('view engine', 'ejs');
    app.set('views', path.join(__dirname, 'views'));
    
    app.use(express.json());
    app.use("/", express.static(path.join(__dirname, "public")));
    app.use(auth(config));
    
    const pool = new Pool({
      connectionString: process.env.DATABASE_URL,
      ssl: {
        require: true,
      },
    });
    
    
    app.get("/", async (req, res) => {
      const client = await pool.connect();
      const response = await client.query('SELECT * FROM student');
      res.render("index", {
        title: "Neon authentication with auth0",
        students: response.rows,
        isAuthenticated: req.oidc.isAuthenticated(),
        user: req.oidc.user,
      });
    })
    
    app.get("/profile", requiresAuth(), (req, res) => {
      res.render("profile", {
        title: "Secured profile page",
        isAuthenticated: req.oidc.isAuthenticated(),
        user: req.oidc.user,
      });
    })
    
    app.listen(process.env.PORT || 3000, () => {
      console.log('Server is running on port 3000');
    })
```

This code does the following:

- `path`: The **path** import is responsible for working with directories and file paths
- The other imports for the app
-  `const app = express()`: It creates a new instance calling the express function on the **app** variable
- `require('dotenv').config()`: this function will read the sensitive information from the `.env` file, which parses the content and assigns it to `process.env`
- `const config`: objects configure the auth0 router the `Express OpenID Connect library`
- `app.set`: sets the view engine as ejs, which is responsible for rendering the page as a web page, and the views is the folder that contains the rendered pages for each page route
- `app.use`: the middlewares have access request (req) and response (res) object
- `app.get(``"``/``"``)`: This is the route handler for the root URL (“/”). Within the callback function is the database query from Neon DB connected using the **student** table and sends a response to the rendered HTML `index.ejs` to the client
- The other GET method with the endpoint, `/profile` is a secured page using the `requiresAuth` middleware which is available only when there is a valid user session 
- The app listens on port 3000 with the app.listen function


## Displaying the View

Let’s create the following directories within the root of the app. For the stylesheets, create a **public** folder and within it, a `style.css` file in a stylesheets folder, which should look like this:


    ├── public
    │   └── stylesheets
    │       └── style.css

 
 Copy-paste this style in the style.css file:
 
```css
    //  public/stylesheets/style.css
    
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      border: 0;
    }
    
    body {
      font-family: 'Inter', sans-serif;
      font-size: 16px;
      line-height: 1.5;
      color: #333;
      background-color: #fff;;
    }
    
    nav {
      padding: 2em 0;
    }
    
    nav li + li {
      margin-top: 1em;
    }
    
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
    main {
      min-height: 100vh;
      object-fit: contain;
      width: 80%;
      margin: 0 auto;
      max-width: 75rem;
    }
    
    .info {
      padding-top: 2em;
      padding-bottom: 2em;
    }
    
    .info h3 {
      margin-bottom: 1.5em;
    }
    
    a {
      text-decoration: none;
      color: #333;
      transition: all 0.3s ease-in;
    }
    
    a:hover {
      color: #3C6C84
    }
    
    img {
      max-width: 100%;
    }
    
    .student-info {
      display: grid;
       grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
      gap: 2rem;
      padding: 2em 0;
    
    }
    
    .student-info ul {
      background: #FBEED3;
      padding: 1em 2em;
      overflow: hidden;
      user-select: none;
    }
    
    .student-info ul li {
      font-weight: 700;
      margin-bottom: .75em;
    }
    
    .profileDetails img {
      border-radius: 50%;
      height: 100px;
      width: 100px;
    }
    
    .profileDetails p {
      margin-bottom: 1em;
    }
    
    pre {
      white-space: auto;
    }
    
    footer {
      background-color: #333;
      color: white;
      padding: 1em 2em;
      width: 100%;
    }
```
 
 Now, for the views, create a folder structure like this:
 

    views
        ├── index.ejs
        ├── partials
        │   ├── footer.ejs
        │   ├── header.ejs
        │   └── nav.ejs
        └── profile.ejs

 
The files in the **partials** folder are reusable code that does not change across the web pages, meaning they are static and included across multiple views or templates using the tag `<%- include(partials/<name-of-file>); -%>`.
 
 Copy-paste the following:
 
```js
    //  views/partials/footer.ejs
    
    <footer>Teri &copy; <%= new Date().getFullYear() %>
    </footer>
```
 
The output value of EJS templates is wrapped tags, which vary depending on the use case.
 
```js
    //  views/partials/header.ejs

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Secure routes with auth0</title>
      <link rel="stylesheet" href="stylesheets/style.css">
    </head>
    
    <body>
```
 
```js
    //  views/partials/nav.ejs
    
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/profile">Profile</a></li>
        <% if (!isAuthenticated) { %>
          <li><a href="/login">Login</a></li>
          <% } else { %>
            <li>
              <a href="/logout">Logout</a>
            </li>
            <% } %>
      </ul>
    </nav>
```
 
The code above, using conditionals, checks when an authenticated user is logged in and displays the right page and navigation menu.
 
```js
    //  views/index.ejs

    <%- include('partials/header'); -%>
      <main>
        <%- include('partials/nav'); -%>
          <div class=" info">
            <% if (user) { %>
              <h3>
                Welcome, <%= user.name %>
              </h3>
    
              <div class=" student-info">
                <% students.forEach(function(student){ %>
                  <ul>
                    <li>
                      <%= student.first_name %>
                        <%= student.last_name %>
                    </li>
                    <p>
                      <%= student.email %>
                    </p>
                  </ul>
                  <% }) %>
              </div>
              <% } else { %>
                <h1>
                  <%= title %>
                </h1>
                <p>You need to login to view the page details</p>
                <% } %>
          </div>
      </main>
      <%- include('partials/footer'); -%>
```
 
This page will display the rendered information from the saved data in Neon in a grid format as well as welcome the user with their name as below:
 

![student data with their name and email addresses](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701988647005_Screenshot+2023-12-07+at+19.03.08.jpg)

 
```js
    //  views/profile.ejs
    
    <%- include('partials/header') -%>
      <main>
        <%- include('partials/nav') -%>
          <div class="info">
            <h2>
              <%= title %>
            </h2>
            <% if (user) { %>
              <div class="profileDetails">
                <p>
                  Hello, <%= user.name %>
                </p>
    
    
                <pre><code><%= JSON.stringify(user,null,2) %></code></pre>
              </div>
              <% } %>
          </div>
      </main>
```
 
The code above includes the **header** and **navigation menu** that displays the user information as an object.
 

![auth0 secured profile page](https://paper-attachments.dropboxusercontent.com/s_4721F701840369CE0B8BF96273ECD4C72516C329EA2BB1E019CA40E2E061FA7F_1701989287852_Screenshot+2023-12-07+at+19.03.40.jpg)


 
[neon with auth0 and ejs - Watch Video](https://www.loom.com/share/99535d8cd3214a1ba284d38921322e18)

![demo app](https://cdn.loom.com/sessions/thumbnails/99535d8cd3214a1ba284d38921322e18-with-play.gif)



## Final Thoughts

In this article, you learned how to set up and connect a PostgreSQL database using Neon and Auth0 to protect and secure web pages, giving access to only logged-in users. 

[source code](https://github.com/Terieyenike/neondb-with-auth0) 

[Live app](https://neondb.onrender.com/)


## Resources
- [Interface with PostgreSQL database](https://node-postgres.com/)
- [Templating language - ejs](https://github.com/mde/ejs)
- [Creating applications with Auth0](https://auth0.com/docs/get-started/auth0-overview/create-applications)
- [Connect a Node.js application to Neon](https://neon.tech/docs/guides/node)
- [VS Code plugin for EJS](https://marketplace.visualstudio.com/items?itemName=DigitalBrainstem.javascript-ejs-support)
- [PostgreSQL data types](https://www.postgresql.org/docs/current/datatype.html)