Authentication has to be done for just about any application, but can be cumbersome to implement, especially if you’re doing it from scratch.
Goal: This guide shows you how to set up a simple web application on your local machine and then use Form.io’s built-in JWT token based authentication to handle user registration and user login for your application..
When I say No Steps Missed, that means every single step is included, with a screen shot, for everything you need to get it working—everything.
Including:
- Setting up a simple local application development environment, even if you’ve never done something like this before
- Creating a Form.io account if you’re not using the enterprise embedded platform
- Configuring your project in Form.io
- Setting up the forms for registration and login
- Testing
Who Needs This Guide?
Form.io helps developers build custom, business process web applications without having to deal with all the frustrating things with forms and data management that are necessary in these types of applications.
This is for developers. Now here’s the thing. Most existing organizations are NOT going to use Form.io’s built-in authentication. Chances are, you’re already using an authentication method and there’s no sense in creating another set of logins for users. Can you integrate your existing authentication method with Form.io? Yes you can.
But if you have a use-case where a pre-built login system is needed, you are just learning, or you just want to test something, this guide is for you.
In either case, Form.io is for developers who want and/or need to build an actual custom web app that doesn’t lock you into a proprietary application development platform (ADP). These platforms will always leave you dependent on them, lock you into their tech stack, and will cost you more as usage of your app increases.
With Form.io, these ceilings are eliminated and with Form.io enterprise, you pay a flat fee based on your configuration and everything can then be embedded in your environment—not just the forms themselves, but all the tools used to build the forms, APIs, form definitions, processing servers, etc.
Form.io:
- Lets you build forms with drag and drop and configure conditional logic and validation without having to code these things yourself for a wide variety of traditional and complex form field components.
- Creates the APIs for you as you build forms and data resources
- Lets you configure how your data will be organized and stored using a form as a model (this is a data resource)
- Has built in authentication, but also allows you to integrate other, existing authentication methods if that’s what you prefer.
All so you can focus on building your app.
A. Setting The Stage
For the purposes of this guide, I will be setting up a simple express.js server that runs locally in my development environment. I am going to set up 3 simple pages:
- login page
- registration page
- an internal page that can only be accessed when the user is successfully logged in.
The authentication will be handled by Form.io’s SaaS offering, but the steps are the same if you are running Form.io Enterprise that’s embedded in your environment.
I’ll be developing on Windows and using these tools, which you will need to install or have equivalents installed:
- Download And Install Node.js for installing code packages that your app will need to run. Installing Node.js will automatically include Node Package Manager (NPM).
- Download And Install Git for the terminal (I won’t be using the GUI)
- Download And Install Visual Studio Code (VS Code) for writing code.
If you are new to this, just select the default configurations for each of the tools as you proceed through their installations.
B. Set Up The Local Development Environment
If you are familiar with setting up a local dev environment, skip to section C.
-
Using a file manager, create a folder on your machine in which you will store and develop the application:
In my example, the folder is called
/testapp
-
Inside of
/testapp
, let’s make a few more folders to organize the application to distinguish between 1) front end files and 2) backend files. Create the following folders:/frontend
, and/backend
-
In the root
/testapp
folder, right-click and select Git Bash Here: -
You can verify that Node and NPM are installed by entering both commands into the Git Bash terminal:
node -v
andnpm -v
(enter them one at a time): -
Initialize your new development project by entering the following command into the terminal:
npm init -y
:This places the file
package.json
in your app folder. -
Type
npm install express
to install express.js. Express.js is used to run server-side (backend) code for your application: -
Launch VS Code on your machine, then select file—open folder:
-
Navigate to
/testapp
then click Select folder: -
Click File—New Text File:
-
Click File—Save As, and name the file
server.js
and save it inside of the/backend
folder that’s inside of/testapp
(/testapp/backend
): -
Within the server.js file, add the following lines of code. This code will call express.js and send a message to the terminal to tell you that the app is running on a specific port.
const express = require('express'); const app = express(); const PORT = process.env.PORT || 4321; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
I have set the port to be
4321
, but you can make this something else (up to 65535), as long as it doesn’t conflict with other things that may be running on your machine. Don’t use port 80 or 443—your browser needs these. -
Click the File Explorer icon in VS Code if it’s not selected already, then select the
package.json
file to open it: -
You will notice some things have been automatically added. Under the
"scripts"
heading, add the line of code specified below. Make sure it ends with a,
(comma):"start": "node backend/server.js",
We are telling node that when we start the application, it should run the code in the
server.js
file we just created. -
In VS Code, click File—New Text File, then click File—Save As.
1. Navigate inside of the
/testapp/frontend
folder
2. Click the New Folder button, and name itpublic
: -
Double-click the
/public
folder to navigate inside of it, then name the fileregister.html
and click Save: -
Click File—New Text File again, click File—Save As, navigate inside of the
/testapp/frontend/public
folder, name the fileindex.html
and click Save. You should now have 2 files inside of/testapp/frontend/public
: -
With
index.html
selected, type!
and press enter to generate the basic HTML structure of a simple web page: -
Change the title from Document to something else if you want:
-
Add
<p>Sign in</p>
inside of the body HTML tags so we will have some text too appear on the screen:At this point, we have an app comprised of a server.js file and 2 flat web pages, one of which is still totally empty.
The app will not have instructions to display these files yet, but that’s okay. Let’s test it to see what happens.
-
Using your Git Bash terminal, type
npm start
and press enter to run your app: -
Open a tab in a browser and navigate to
http://localhost:4321/
. Notice that you see the messageCannot GET /
:This means your app is running, but it can’t do anything.
-
In VS Code, select
server.js
and add the lineconst path = require('path');
near the top:You will notice I changed the title and I added some simple paragraph text in the body.
-
Add the following lines of code to your server.js file as well:
app.use(express.json()); // To parse incoming JSON payloads // Serve static files from the /frontend/public directory app.use(express.static(path.join(__dirname, '../frontend/public')));
The server.js file will now let your app access anything inside of
/frontend/public
. By default, whatever files you place inside of/public
will be accessible by your app without having to sign in. As we proceed, we will create an additional file that can only be accessed once a user is logged in.We also added the line to parse incoming JSON which we’ll need later.
Now that we’ve updated the server code, we need to restart Node.js.
-
In your Git Bash terminal, press CTRL+C to cancel running the server:
-
Type
npm start
again to restart the server: -
In your browser, refresh the page where you have
http://localhost:4321/
running. You will now see the Sign In text we wrote earlier: -
In VS Code, select the
register.html
file and type!
and press enter to generate the basic HTML structure of a simple web page:You will notice I changed the title and I added some simple paragraph text in the body.
-
Let’s make these pages a bit nicer. In VS Code, click File—New Text File, then click Save-As and save it inside of the
/public
folder and name itstyle.css
: -
By default, browsers will render HTML elements with certain built-in. Let’s reset all the styles so we can start with a blank slate. Add the following code to the
style.css
file:/* =Reset default browser CSS. -------------------------------------------------------------- */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { border: 0; font-family: inherit; font-size: 100%; font-style: inherit; font-weight: inherit; margin: 0; outline: 0; padding: 0; vertical-align: baseline; } img { border: 0px; } li { list-style-type: none; } /* ================== EVERYTHING is BOX-SIZED - position relative ================== */ * { -webkit-text-size-adjust: none; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -ms-box-sizing: border-box; position: relative; } /* ================== EVERYTHING is BOX-SIZED - position relative ================== */
-
Copy the code below and paste it into your
index.html
file. This will load the style sheet we just created as well as a Google font.<!-- CSS --> <link rel="stylesheet" href="style.css"> <!-- Fonts --> <link href="<https://fonts.googleapis.com/css2?family=Inter:wght;400;500;600;700;800;900&display=swap>" rel="stylesheet">
Copy this
Paste the code inside of the
<head>
…</head>
tag of yourindex.html
file. -
Copy the CSS code below and paste it to the bottom of your
style.css
file:body { font-size: 14px; font-family: Inter, sans-serif; background-color: #f3f3f8; } .form-wrap { width: 400px; height: 500px; border-radius: 5px; box-shadow: 0 0 40px rgba(0,0,0,.1); position: fixed; top: 0; right: 0; bottom: 0; left: 0; margin: auto; background-color: #fff; padding: 30px; }
-
Copy this HTML and replace
<p>Sign in</p>
that’s inside ofindex.html
with it:<div class="form-wrap"> <p>Sign in.</p> </div>
-
In your browser, refresh the page to view it:
-
Repeat steps 30 and 32 above for the
register.html
page. Make sure to change the text to Register:There are more changes needed to these files in order to get everything working, which we will come back to, but for now, let’s move on to configuring Form.io to handle user authentication.
C. Finish Building The Pages And Add The Login And Register Forms To Your Application
-
Sign into the Form.io Developer Portal at
portal.form.io
-
If you are using Form.io Enterprise embedded in your environment, the URL to access the Developer Portal will be unique to your organization. If you would like to use the Form.io SaaS offering or just test things out, you can create an account at portal.form.io (30 day trial). In either case, the steps will be the same.
-
Once logged in, go to Projects and click Create Project:
-
Name the project whatever you want and click Create project. This should automatically open the newly created project.
-
-
If necessary, open the project you are working on by clicking the project name:
To get back to the list of projects, click the Form.io logo at the top left.
-
By default, Form.io automatically generates the necessary Forms and Resources to handle authentication when a new project is created. Structurally, forms and resources are the same thing, but think of forms as the UI elements on the page and resources as where the data is actually stored.
Select the User Login Form:
-
Click Embed, then copy the HTML for where the login form will appear and paste it in your
index.html
file in VS Code:Copy this
Select the
index.html
file and paste it below the Sign In text. Change the ID to be login instead of formio. -
In Form.io, copy the code that attaches the form to the HTML element we just added, then paste it right before the closing
</body>
tag:Copy this
Paste this here. Make sure the single quotes surrounding the word login remain intact.
Make sure to replace
formio
withlogin
so it matches the element we created in the previous step.Now, this is NOT going to work yet, because our app is not loading the formio library, among other things.
-
Your server should still be running in your Git Bash terminal:
Instead of cancelling it to run a command, let’s open a second instance of Git Bash so we can use one terminal to run the server and a second terminal to enter new commands.
In your file manager, navigate to the root of the
/testapp
folder, right-click in an empty area, and click Git Bash Here:We now have 2 Git windows running. The top one is where the server is running. The bottom one is free to accept commands.
-
We will need the
axios
library in the next section. It provides a clean promised-based API for sending asynchronous requests to REST endpoints. Form.io forms and resources are REST endpoints, so we will need it to verify a user’s token when they sign in.To install it:
-
Run
npm install axios
in your second git window. -
Add
const axios = require('axios');
to the top of your server.js file.
-
-
In order to render the forms, we need to load the
formiojs
library. We can call it from our front-end files using Formio’s content delivery network (CDN). Add the following line of code inside the<head>
tag of:index.html, AND
register.html<script src="https://cdn.form.io/formiojs/formio.full.min.js"></script>
-
In your browser, refresh your app to see the login form now renders:
Works. Doesn’t look great.
-
Optional: Copy the CSS below and add it to the bottom of your style.css file:
.form-wrap h1 { font-weight: bold; font-size: 22px; } .formio-form { display: flex; gap: 20px; flex-direction: column; margin-top: 20px; } .formio-form .form-group.formio-component div[ref="element"] { margin-top: 5px; } .formio-form label { font-weight: bold; cursor: pointer; } .formio-form input[type=email], .formio-form input[type=password], .formio-form input[type=text], .formio-form input[type=texarea] { width: 100%; padding: 10px 15px; border: 1px solid #e1e1e6; border-radius: 5px; font-family: Inter, sans-serif; background-color: #f3f3f8; transition: .15s linear all; } .formio-form input[type=email]:focus, .formio-form input[type=password]:focus, .formio-form input[type=text]:focus, .formio-form input[type=texarea]:focus { border: 1px solid #69b342; outline: none; transition: .15s linear all; } button { border: 0; border-radius: 5px; line-height: 1em; font-weight: bold; font-family: Inter, sans-serif; font-size: 14px; background-color: #69b342; color: #fff; padding: 10px 20px; cursor: pointer; transition: .15s linear all; } button.logout { margin-top: 20px; } button:hover { background-color: #78cd4b; transition: .15s linear all; }
-
In index.html, replace
<p>Sign in.</p>
with<h1>Sign in.</h1>
and in register.html, replace<p>Register</p>
with<h1>Register</h1>
: -
Refresh your app in your browser to take a look:
Much better.
-
Let’s place the registration form on the register.html page. In Form.io, click Forms—User Register:
-
Click Embed at the top right:
-
Copy this line of code, paste it right under the
<h1>Register</h1>
line, then rename the ID value to register : -
Copy this code and paste it right before the closing
</body>
tag in your register.html file:Make sure to replace formio with register so the ID in both the HTML element and the JavaScript match:
-
In your browser, add a
/register.html
to the end of the URL and press enter to test the register page:Looks good.
-
Let’s create a new page that will be “behind” the login. This page will only be accessible when the user logs in. In VS Code:
Click File—New Text File
Click File—Save As
Navigate to the/frontend
folder
Name the fileapp.html
and click Save. -
Copy the code below and paste it in
app.html
:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Test App</title> <!-- CSS --> <link rel="stylesheet" href="style.css"> <!-- Fonts --> <link href="<https://fonts.googleapis.com/css2?family=Inter:wght;400;500;600;700;800;900&display=swap>" rel="stylesheet"> <script src="<https://cdn.form.io/formiojs/formio.full.min.js>"></script> </head> <body> <div class="form-wrap"> <h1>You are logged in.</h1> <button class="logout" onclick="signout();">Sign Out</button> </div> </body> </html>
This is just a simple page with the message “You are logged in,” to show the user that they are logged in. If you try to navigate to /app.html, it won’t work because the app does not have access to it yet:
D. Configure Your App To Read And Verify The Login Token
When a user successfully logs in, the browser will receive a token (a long, secure hash) that indicates a valid user. Form.io captures and stores this in local storage automatically (which we’ll also store in a cookie). We can then check if there’s a valid login against the token before loading app.html
, which represents our internal app.
-
In your second Git window, type
npm install cookie-parser
which will manage cookies: -
Add the following lines of code to the top of your
server.js
file:const cookieParser = require('cookie-parser'); app.use(cookieParser());
-
Copy the code below, then add it to the bottom of your
index.html
file:<script> // Log the user in Formio.createForm(document.getElementById('login'), 'YOUR LOGIN FORM ENDPOINT URL') .then(function(form) { // Allow Enter key to submit form form.element.addEventListener('keypress', function(e) { var key = e.which || e.keyCode; if (key === 13) { // 13 is the Enter key form.submit(); } }); // What to do when the submit begins. form.on('submitDone', function(submission) { const token = localStorage.getItem('formioToken'); if (token) { // Set the token in a cookie document.cookie = "token=" + token + ";path=/;samesite=strict"; // Redirect or perform other actions as needed setTimeout(() => { window.location = 'app.html'; }, 200); } else { console.error('Token not found in local storage'); } }); }); </script>
We are going to replace the old
<script>
at the bottom with the new one.Copy the endpoint URL from the old script and replace the text [YOUR LOGIN FORM ENDPOINT].
Delete the old script tag so all you have at the bottom is this:This does 3 things:
- Lets you press enter to hit the submit button
- Stores the token that it captures into a cookie
- Redirects you to app.html after a successful login.
-
Copy the code below and add it to the bottom of your
server.js
file:// Middleware to verify token async function verifyTokenMiddleware(req, res, next) { const token = req.cookies.token; if (!token) { return res.redirect('/index.html'); } const isValid = await isTokenValid(token); if (!isValid) { return res.redirect('/index.html'); } next(); } async function isTokenValid(token) { const formioAPIEndpoint = '[USER RESOURCE ENDPOINT HERE]'; try { const response = await axios.get(formioAPIEndpoint, { headers: { 'Authorization': `Bearer ${token}` } }); return response.status === 200; } catch (error) { return false; } } // Serve app.html after verifying token app.get('/app.html', verifyTokenMiddleware, (req, res) => { res.sendFile(path.resolve(__dirname, '../frontend/app.html')); });
Here’s what’s happening:
The first function will check if the token exists and if it does has it been validated. If the token does not exist or it’s not valid, then the user will be redirected to the
index.html
sign in page.
The second function is what actually validates the token with the Form.io endpoint, which we will populate shortly (notice the[USER RESOURCE ENDPOINT HERE]
text).
The third part will open access toapp.html
if the token is present and valid.Now we need to specify your Form.io login endpoint.
-
In Form.io, click Resources, then select the User resource:
-
At the top right, click Form API:
-
Copy the Form Endpoint URL (first one) and then replace
[ENDPOINT HERE]
in your server.js file with this URL:Copy this URL
Paste it in this function.
-
The User Login form in Form.io actually checks against the User resource. By default, the login form is configured, but we need to allow access to authenticated users to access the User resource.
Click Access at the top right (while viewing the User resource):
-
Change the following settings:
-
Create Own Submissions: Anonymous:
Read Own Submissions: Authenticated
Update Own Submissions: Authenticated
Delete Own Submissions: Authenticated
Click Save Settings at the bottom right:
-
-
Copy the code below and add it right before the closing
</head>
tag in yourindex.html
file:<script type="text/javascript"> // Check if token cookie exists const token = localStorage.getItem('formioToken') // If token exists, redirect to app.html if (token) { window.location = 'app.html'; } </script>
This code will check if the user is already logged in when they load index.html. If they are, then they will be redirected to
app.html
.Let’s test it out before setting up registration. We need an existing user in order to test, so we’ll make one directly in Form.io.
-
We also need to validate the token once the user accesses the
app.html
page. Copy the code below and paste it in yourapp.html
file right before the closing</head>
tag:<!-- Verify Token --> <script> // Check if token cookie exists const token = localStorage.getItem('formioToken') // If token exists, redirect to app.html if (!token) { window.location.href = 'login.html'; } window.logout = function() { // Remove the authentication token from local storage or cookies localStorage.removeItem('formioToken'); // Optionally, also remove the token from Form.io's own storage Formio.clearCache(); Formio.setUser(null); document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; // Redirect the user to the index/login page window.location.href = 'index.html'; // Replace 'index.html' with your login page's URL }; </script>
I have also taken the liberty to add the code that will make the logout button work.
-
In the same User resource, click the Use tab, then enter a fake email and made up password, like
123456
, then click submit. -
IMPORTANT: We’ve made a lot of significant changes to our app. We should restart node. In your first Git Bash window, press CTRL+C to cancel running the server, then type
npm start
again to restart the server: -
In your browser, press CTRL+F5 (or COMMAND+R) on a mac to hard refresh the page, then login with the user you just created:
-
If everything worked correctly, you should be automatically redirected to app.html:
And clicking the Log Out button will bring you back to the login page:
And if you try to access app.html manually, while logged out, you will be immediately redirected to index.html.
So the basic structure of logging in and accessing our app is working. Now all we need to do is a few final things to get user registration to work.
E. Configure User Registration For Your Application
-
In your index.html file, add the following line of code right after
<div id="login"></div>
:<p style="margin-top: 20px;">Or <a href="register.html">Register</a> for a new account.</p>
-
In your register.html file, add the following line of code right after
<div id="register"></div>
<p style="margin-top: 20px;">Or <a href="index.html">Login</a> with an existing account.</p>
-
Copy the following code below and paste it in your register.html file right before the closing
</body>
tag at the bottom:<script> // Create the registration form Formio.createForm(document.getElementById('register'), '[YOUR REGISTER FORM ENDPOINT]') .then(function(form) { // Allow Enter key to submit form form.element.addEventListener('keypress', function(e) { var key = e.which || e.keyCode; if (key === 13) { // 13 is the Enter key form.submit(); } }); // What to do when the registration is successful form.on('submitDone', function(submission) { const token = localStorage.getItem('formioToken'); if (token) { // Set the token in a cookie document.cookie = "token=" + token + ";path=/;samesite=strict"; // Redirect or perform other actions as needed setTimeout(() => { window.location = 'index.html'; }, 200); } else { console.error('Token not found in local storage'); } }); }); </script>
-
Copy your User Register form endpoint URL and replace the text [YOUR REGISTER FORM ENDPOINT] in the new script tag, then delete the old script:
This new script will:
- Let you press enter to submit
- Store the token in local storage and in a cookie.
- Will redirect to app.html (through the redirect on index.html if necessary).
-
Copy the code below and paste it right before the closing
</head>
tag in your register.html file:<script type="text/javascript"> // Check if token cookie exists const token = localStorage.getItem('formioToken') // If token exists, redirect to app.html if (token) { window.location = 'app.html'; } </script>
If the register page detects a valid token, it will redirect the user to app.html.
-
Refresh your browser’s index/sign in page to see the new link and text:
-
Before we test the registration, let’s change the text on the buttons. In Form.io, click Forms—User Login, then hover over the submit button and click the small gear icon:
-
Change the label to Sign In or Login, then click Save. You probably should pick something consistent even though I didn’t.
-
Click Save Form at the bottom right:
-
Repeat steps 6-8 for the Registration form button. I went with Register.
-
Click the register link to navigate to the register page:
-
Enter a new, fake email account, a simple password, and click Register:
And you are redirected to the app. If the redirect fails, just try to access app.html manually.