Building Secure Neon-Infused Web Apps with Auth0, Express, and EJS
oteri
Posted on December 18, 2023
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 >= 20 on your local machine
- Familiarity with using Embedded JavaScript (EJS)
- An Auth0 account for creating an application with authentication
Check out the source code and the live app 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!
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.
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.
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:
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:
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 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:
SELECT * FROM student;
Click on the Tables menus to see the data arranged in rows and columns as shown below:
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 or Windows, 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.
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:
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:
select * from student;
You should see something like this:
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 in this tutorial.
To create a new application, navigate to the Applications menu and select Applications from the dropdown.
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.
The most crucial step of this setup is choosing a technology for the project. Select Node.js (Express).
Installation and setup
Make a new directory in the terminal by running:
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:
{
"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:
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.
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:
// 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 toprocess.env
-
const config
: objects configure the auth0 router theExpress 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 HTMLindex.ejs
to the client - The other GET method with the endpoint,
/profile
is a secured page using therequiresAuth
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:
// 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:
// views/partials/footer.ejs
<footer>Teri © <%= new Date().getFullYear() %>
</footer>
The output value of EJS templates is wrapped tags, which vary depending on the use case.
// 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>
// 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.
// 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:
// 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.
neon with auth0 and ejs - Watch Video
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.
Resources
Posted on December 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.