Transmits a Site ID and ID Token
Send a Site ID and ID Token from the Designer Extension to the Data Client for verification.
Resolves the ID Token on the Server Side
The Data Client resolves the ID Token to get user details from Webflow and verify access.
Maps user authorization
Establish a link between the user and their authorized sites, allowing for secure access to site data.
Creates a Session Token for the user
Generate and send a session token to the Designer Extension to securely maintain the user’s session and enable authenticated requests.
Makes authenticated requests to Webflow’s Data APIs
Use the session token and stored tokens to make authenticated API calls to Webflow’s Data APIs.
By the end, you’ll have a secure, fully integrated setup to handle user sessions and seamlessly make requests to external APIs.
Prerequisites
A Hybrid App with the following scopes: sites:read, authorized_user:read
Otherwise, you can clone the repository from GitHub.com
Install dependencies
This example contains both a Designer Extension and Data Client project. Input the following commands in your terminal to install all necessary dependencies for the example.
$ cd hybrid-app-authentication
npm install
npm run install-frontend
npm run install-backend
Add environment variables
main.js
server.js
database.js
jwt.js
.env
_4
WEBFLOW_CLIENT_ID=XXX
_4
WEBFLOW_CLIENT_SECRET=XXX
_4
PORT=3000
_4
NGROK_AUTH_TOKEN=XXX
Replace the example values in the .env.example file with your credentials. Rename the file to .env
Review Designer Extension
main.js
server.js
database.js
jwt.js
.env
_145
import React, { useState, useEffect } from "react";
_145
import ReactDOM from "react-dom/client";
_145
import axios from "axios";
_145
import DataTable from "./DataTable";
_145
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
In this tutorial, most of our focus will be on setting up and configuring the Data Client. However, to kick off the authentication process, we need to first send an ID token and Site ID from the Designer Extension to the Data Client. This step will allow us to exchange these tokens for a session token, which is required for making authenticated requests to Webflow’s API from the browser.
To get started, review the exchangeAndVerifyIdToken function in the Designer Extension.
Retrieving the idToken from the Designer Extension.
main.js
server.js
database.js
jwt.js
.env
_145
import React, { useState, useEffect } from "react";
_145
import ReactDOM from "react-dom/client";
_145
import axios from "axios";
_145
import DataTable from "./DataTable";
_145
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
The exchangeAndVerifyIdToken function initiates the authentication process by using the Webflow Designer API’s getIdToken and getSiteInfo methods to retrieve the idToken and siteId from the Designer Extension.
Exchanging the idToken and siteId for a Session Token
main.js
server.js
database.js
jwt.js
.env
_145
import React, { useState, useEffect } from "react";
_145
import ReactDOM from "react-dom/client";
_145
import axios from "axios";
_145
import DataTable from "./DataTable";
_145
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
With the ID token and Site ID in hand, the Designer Extension then sends this information to an endpoint on the Data Client.
The Data Client verifies the ID token and returns a session token, which the Designer Extension will use to make authenticated requests to Webflow. This token ensures secure, temporary access and allows the Designer Extension to manage user sessions as we continue through the setup.
Set up your server
Switching to the Data Client, let’s quickly set up an Express server to handle incoming requests and prepare for adding authentication in Data Client/server.js.
Initialize Express
Create your server with Express, configure CORS to accept incoming requests from your Designer, and set up middleware for JSON and URL-encoded requests
main.js
server.js
database.js
jwt.js
.env
_20
const express = require("express");
_20
const cors = require("cors");
_20
const { WebflowClient } = require("webflow-api");
_20
const axios = require("axios");
_20
require("dotenv").config();
_20
_20
const app = express(); // Create an Express application
_20
_20
var corsOptions = { origin: ["http://localhost:1337"] };
_20
_20
// Middleware
_20
app.use(cors(corsOptions)); // Enable CORS with the specified options
console.log(`Server is running on http://localhost:${PORT}`);
_20
});
Configure authorization flow
Add an endpoint on your server to handle the OAuth callback and retrieve an authorization_code from the URI’s query parameters. Then, create a /callback endpoint to exchange the code for an accessToken for your user.
console.log(`Server is running on http://localhost:${PORT}`);
_61
});
In addition to retrieving an accessToken through OAuth, your Designer Extension will also receive an idToken and a siteId during the authentication process for the Designer Extension. These tokens enable you to verify and authorize user access securely.
To manage this, you’ll need to set up a new endpoint that:
Retrieves an accessToken associated with the Site ID from a database
Generates a Session Token for secure, temporary access.
Stores the accessToken in your database, associating it with the user or site.
Sends the Session Token to the Designer Extension
For now, we’ll set up the endpoint to receive the ID Token. We’ll add the database storage, access token retrieval, and session token generation in the next steps.
Set up your database to save and retrieve credentials
main.js
server.js
database.js
jwt.js
.env
_186
import sqlite3 from "sqlite3";
_186
import jwt from "jsonwebtoken";
_186
import dotenv from "dotenv";
_186
_186
// Enable dotenv to load environment variables
_186
dotenv.config();
_186
_186
// Enable verbose mode for SQLite3
_186
const sqlite3Verbose = sqlite3.verbose();
_186
_186
// Open SQLite database connection
_186
const db = new sqlite3Verbose.Database("./db/database.db");
_186
_186
// Create tables
_186
db.serialize(() => {
_186
_186
// Table to associate site ID with access token from OAuth
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS siteAuthorizations (
_186
siteId TEXT PRIMARY KEY,
_186
accessToken TEXT
_186
)
_186
`);
_186
_186
// Table to associate user ID with the access token
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS userAuthorizations (
_186
id INTEGER PRIMARY KEY AUTOINCREMENT,
_186
userId TEXT,
_186
accessToken TEXT
_186
)
_186
`);
_186
});
_186
_186
// Insert a record after exchanging the OAuth code for an access token
_186
function insertSiteAuthorization(siteId, accessToken) {
_186
db.get(
_186
"SELECT * FROM siteAuthorizations WHERE siteId = ?",
_186
[siteId],
_186
(err, existingAuth) => {
_186
if (err) {
_186
console.error("Error checking for existing site authorization:", err);
_186
return;
_186
}
_186
_186
// If the user already exists, return the existing user
Now you've set up your server to retreive an access token, let’s configure a database to store user credentials and authorization details in database.js.
Set up the database schema and tables
First, you’ll create the necessary database schema to store site and user authorizations. This includes two main tables to associate site IDs and user IDs with their respective access tokens.
Store authorization data
main.js
server.js
database.js
jwt.js
.env
_186
import sqlite3 from "sqlite3";
_186
import jwt from "jsonwebtoken";
_186
import dotenv from "dotenv";
_186
_186
// Enable dotenv to load environment variables
_186
dotenv.config();
_186
_186
// Enable verbose mode for SQLite3
_186
const sqlite3Verbose = sqlite3.verbose();
_186
_186
// Open SQLite database connection
_186
const db = new sqlite3Verbose.Database("./db/database.db");
_186
_186
// Create tables
_186
db.serialize(() => {
_186
_186
// Table to associate site ID with access token from OAuth
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS siteAuthorizations (
_186
siteId TEXT PRIMARY KEY,
_186
accessToken TEXT
_186
)
_186
`);
_186
_186
// Table to associate user ID with the access token
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS userAuthorizations (
_186
id INTEGER PRIMARY KEY AUTOINCREMENT,
_186
userId TEXT,
_186
accessToken TEXT
_186
)
_186
`);
_186
});
_186
_186
// Insert a record after exchanging the OAuth code for an access token
_186
function insertSiteAuthorization(siteId, accessToken) {
_186
db.get(
_186
"SELECT * FROM siteAuthorizations WHERE siteId = ?",
_186
[siteId],
_186
(err, existingAuth) => {
_186
if (err) {
_186
console.error("Error checking for existing site authorization:", err);
_186
return;
_186
}
_186
_186
// If the user already exists, return the existing user
Next, you’ll store the Site IDs and user access tokens in the database using functions that insert new records if they don’t already exist.
Store Site Authorization Data with insertSiteAuthorization
Use this function to pair a siteId with its access token when a site is initially authorized.
Store User Authorization Data with insertUserAuthorization
Use this function to pair a userId and access token.
Retrieve authorization data
main.js
server.js
database.js
jwt.js
.env
_186
import sqlite3 from "sqlite3";
_186
import jwt from "jsonwebtoken";
_186
import dotenv from "dotenv";
_186
_186
// Enable dotenv to load environment variables
_186
dotenv.config();
_186
_186
// Enable verbose mode for SQLite3
_186
const sqlite3Verbose = sqlite3.verbose();
_186
_186
// Open SQLite database connection
_186
const db = new sqlite3Verbose.Database("./db/database.db");
_186
_186
// Create tables
_186
db.serialize(() => {
_186
_186
// Table to associate site ID with access token from OAuth
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS siteAuthorizations (
_186
siteId TEXT PRIMARY KEY,
_186
accessToken TEXT
_186
)
_186
`);
_186
_186
// Table to associate user ID with the access token
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS userAuthorizations (
_186
id INTEGER PRIMARY KEY AUTOINCREMENT,
_186
userId TEXT,
_186
accessToken TEXT
_186
)
_186
`);
_186
});
_186
_186
// Insert a record after exchanging the OAuth code for an access token
_186
function insertSiteAuthorization(siteId, accessToken) {
_186
db.get(
_186
"SELECT * FROM siteAuthorizations WHERE siteId = ?",
_186
[siteId],
_186
(err, existingAuth) => {
_186
if (err) {
_186
console.error("Error checking for existing site authorization:", err);
_186
return;
_186
}
_186
_186
// If the user already exists, return the existing user
Finally, you’ll retrieve the stored access tokens using functions designed to fetch access tokens based on siteId or userId.
Retrieve Site Access Tokens with getAccessTokenFromSiteId
This function retrieves the access token for a specific siteId. We'll use this function to obtain user details from Webflow's Resolve ID Token endpoint when a Designer Extension sends an idToken and siteId to our /token endpoint.
Retrieve User Access Tokens with getAccessTokenFromUserId
Use this function to get a user-specific access token, enabling authenticated access to the Webflow API based on the userId.
Export functions
main.js
server.js
database.js
jwt.js
.env
_186
import sqlite3 from "sqlite3";
_186
import jwt from "jsonwebtoken";
_186
import dotenv from "dotenv";
_186
_186
// Enable dotenv to load environment variables
_186
dotenv.config();
_186
_186
// Enable verbose mode for SQLite3
_186
const sqlite3Verbose = sqlite3.verbose();
_186
_186
// Open SQLite database connection
_186
const db = new sqlite3Verbose.Database("./db/database.db");
_186
_186
// Create tables
_186
db.serialize(() => {
_186
_186
// Table to associate site ID with access token from OAuth
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS siteAuthorizations (
_186
siteId TEXT PRIMARY KEY,
_186
accessToken TEXT
_186
)
_186
`);
_186
_186
// Table to associate user ID with the access token
_186
db.run(`
_186
CREATE TABLE IF NOT EXISTS userAuthorizations (
_186
id INTEGER PRIMARY KEY AUTOINCREMENT,
_186
userId TEXT,
_186
accessToken TEXT
_186
)
_186
`);
_186
});
_186
_186
// Insert a record after exchanging the OAuth code for an access token
_186
function insertSiteAuthorization(siteId, accessToken) {
_186
db.get(
_186
"SELECT * FROM siteAuthorizations WHERE siteId = ?",
_186
[siteId],
_186
(err, existingAuth) => {
_186
if (err) {
_186
console.error("Error checking for existing site authorization:", err);
_186
return;
_186
}
_186
_186
// If the user already exists, return the existing user
res.status(500).json({ error: "Internal server error" });
_115
}
_115
});
_115
_115
// Start the server
_115
const PORT = process.env.PORT || 3000;
_115
app.listen(PORT, () => {
_115
console.log(`Server is running on http://localhost:${PORT}`);
_115
});
Update the /callback endpoint in server.js to exchange the code for an accessToken.
To get a list of Sites that the App is authorized to access, instatiate the WebflowClient and call the List Sites endpoint. For each authorized site, use the db.insertSiteAuthorization function to store the siteId and corresponding accessToken in the database for secure, future access.
Redirect the User to the Designer Extension using a deep link
main.js
server.js
database.js
jwt.js
.env
_115
const express = require("express");
_115
const cors = require("cors");
_115
const { WebflowClient } = require("webflow-api");
_115
const axios = require("axios");
_115
require("dotenv").config();
_115
_115
const app = express(); // Create an Express application
_115
const db = require("./database.js"); // Load DB Logic
_115
const jwt = require("./jwt.js")
_115
_115
var corsOptions = { origin: ["http://localhost:1337"] };
_115
_115
// Middleware
_115
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_115
}
_115
});
_115
_115
// Start the server
_115
const PORT = process.env.PORT || 3000;
_115
app.listen(PORT, () => {
_115
console.log(`Server is running on http://localhost:${PORT}`);
_115
});
This link allows the user to seamlessly continue within your App in the Webflow Designer. For now, the example below redirects to the first available site, but you could enhance the UX by allowing users to select a specific site before redirecting.
Integrate JWT for secure session management
main.js
server.js
database.js
jwt.js
.env
_71
import jwt from "jsonwebtoken";
_71
import db from "./database.js";
_71
_71
// Given a site ID, retrieve associated Access Token
_71
const retrieveAccessToken = (req, res, next) => {
_71
const idToken = req.body.idToken;
_71
const siteId = req.body.siteId;
_71
_71
if (!idToken) {
_71
return res.status(401).json({ message: "ID Token is missing" });
_71
}
_71
if (!siteId) {
_71
return res.status(401).json({ message: "Site ID is missing" });
.json({ error: "Failed to retrieve access token" });
_71
}
_71
// Attach access token in the request object so that you can make an authenticated request to Webflow
_71
req.accessToken = accessToken;
_71
_71
next(); // Proceed to next middleware or route handler
_71
});
_71
});
_71
};
_71
_71
export default {
_71
createSessionToken,
_71
retrieveAccessToken,
_71
authenticateSessionToken,
_71
};
With our database configured to handle access tokens, it’s time to implement JSON Web Tokens (JWT) to securely manage sessions and authenticate requests in the Data Client.
JWT will enable us to issue session tokens and validate them, allowing for a secure, stateless authentication flow.
Setting up JWT middleware
main.js
server.js
database.js
jwt.js
.env
_71
import jwt from "jsonwebtoken";
_71
import db from "./database.js";
_71
_71
// Given a site ID, retrieve associated Access Token
_71
const retrieveAccessToken = (req, res, next) => {
_71
const idToken = req.body.idToken;
_71
const siteId = req.body.siteId;
_71
_71
if (!idToken) {
_71
return res.status(401).json({ message: "ID Token is missing" });
_71
}
_71
if (!siteId) {
_71
return res.status(401).json({ message: "Site ID is missing" });
.json({ error: "Failed to retrieve access token" });
_71
}
_71
// Attach access token in the request object so that you can make an authenticated request to Webflow
_71
req.accessToken = accessToken;
_71
_71
next(); // Proceed to next middleware or route handler
_71
});
_71
});
_71
};
_71
_71
export default {
_71
createSessionToken,
_71
retrieveAccessToken,
_71
authenticateSessionToken,
_71
};
In jwt.js, start by importing the jsonwebtoken library and your custom database module. These imports will allow you to handle JWT creation and validation, as well as access and store authorization data in your database.
Retrieve the Access Token based on the Site ID
main.js
server.js
database.js
jwt.js
.env
_71
import jwt from "jsonwebtoken";
_71
import db from "./database.js";
_71
_71
// Given a site ID, retrieve associated Access Token
_71
const retrieveAccessToken = (req, res, next) => {
_71
const idToken = req.body.idToken;
_71
const siteId = req.body.siteId;
_71
_71
if (!idToken) {
_71
return res.status(401).json({ message: "ID Token is missing" });
_71
}
_71
if (!siteId) {
_71
return res.status(401).json({ message: "Site ID is missing" });
.json({ error: "Failed to retrieve access token" });
_71
}
_71
// Attach access token in the request object so that you can make an authenticated request to Webflow
_71
req.accessToken = accessToken;
_71
_71
next(); // Proceed to next middleware or route handler
_71
});
_71
});
_71
};
_71
_71
export default {
_71
createSessionToken,
_71
retrieveAccessToken,
_71
authenticateSessionToken,
_71
};
Create the retrieveAccessToken function to obtain the Access Token associated with a given siteId using the db.getAccssTokenFromSiteId function we created in our database module.
When the Designer Extension passes a siteId and idToken to our /token endpoint in the Data Client, we'll use this middleware to retreive the Access Token associated with the siteId and attach it to the request object. This way, subsequent steps in the endpoint have access to the token, allowing the Data Client to securely interact with Webflow’s API.
Create the Session Token from User Data
main.js
server.js
database.js
jwt.js
.env
_71
import jwt from "jsonwebtoken";
_71
import db from "./database.js";
_71
_71
// Given a site ID, retrieve associated Access Token
_71
const retrieveAccessToken = (req, res, next) => {
_71
const idToken = req.body.idToken;
_71
const siteId = req.body.siteId;
_71
_71
if (!idToken) {
_71
return res.status(401).json({ message: "ID Token is missing" });
_71
}
_71
if (!siteId) {
_71
return res.status(401).json({ message: "Site ID is missing" });
.json({ error: "Failed to retrieve access token" });
_71
}
_71
// Attach access token in the request object so that you can make an authenticated request to Webflow
_71
req.accessToken = accessToken;
_71
_71
next(); // Proceed to next middleware or route handler
_71
});
_71
});
_71
};
_71
_71
export default {
_71
createSessionToken,
_71
retrieveAccessToken,
_71
authenticateSessionToken,
_71
};
The createSessionToken function generates a session token using JWT based on user data from the resolved idToken.
This sessionToken, which includes the user’s details, will later be verified for secure access. The token is set to expire after a defined time (in this example, 24 hours), ensuring that the session remains secure and temporary.
Authenticate the Session Token to Validate User Access on Each Request
main.js
server.js
database.js
jwt.js
.env
_71
import jwt from "jsonwebtoken";
_71
import db from "./database.js";
_71
_71
// Given a site ID, retrieve associated Access Token
_71
const retrieveAccessToken = (req, res, next) => {
_71
const idToken = req.body.idToken;
_71
const siteId = req.body.siteId;
_71
_71
if (!idToken) {
_71
return res.status(401).json({ message: "ID Token is missing" });
_71
}
_71
if (!siteId) {
_71
return res.status(401).json({ message: "Site ID is missing" });
.json({ error: "Failed to retrieve access token" });
_71
}
_71
// Attach access token in the request object so that you can make an authenticated request to Webflow
_71
req.accessToken = accessToken;
_71
_71
next(); // Proceed to next middleware or route handler
_71
});
_71
});
_71
};
_71
_71
export default {
_71
createSessionToken,
_71
retrieveAccessToken,
_71
authenticateSessionToken,
_71
};
The authenticateSessionToken function is middleware that validates the sessionToken provided in the authorization header of each request. By verifying the sessionToken, this function ensures that only authorized users can access protected routes. Upon successful validation, it retrieves the accessToken for the user and attaches it to the request, allowing further secure interactions with Webflow.
Export these functions once they are ready.
Using JWT middleware in the server
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
Now that you’ve set up the JWT and database functions, you can update the /token endpoint in server.js to securely authenticate the user. This endpoint will:
Retrieve the accessToken associated with the user's siteId
Get user details from the resolved idToken
Generate a Session Token from user details
Store the authorization details
See the steps below for more detail.
1. Retrieve the accessToken associated with the user's siteId
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
The jwt.retrieveAccessToken middleware fetches the accessToken associated with the siteId and attaches it to the request, ensuring secure access for the Webflow API call.
2. Get user details from the resolved idToken
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
Using the accessToken, this endpoint sends an authenticated request to Webflow’s Resolve ID Token endpoint, which validates the idToken received from the Designer Extension, and returns the following user details: id, email, firstName, and lastName.
3. Generate a Session Token
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
With the newly obtained user information, the jwt.createSessionToken function creates a Session Token for the authenticated user, establishing a secure, temporary access session.
4. Store Authorization Details
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
Finally, the retrieved accessToken is stored in the database and associated with the userId using db.insertUserAuthorization, enabling easy access and authorization for subsequent requests
Create a protected endpoint for authenticated Webflow requests
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
To demonstrate how to make secure, protected requests, let's set up an endpoint that requires a Session Token for access. We'll create a new /sites endpoint that will:
Authenticate the Session Token
Access Webflow data in the Data Client
Return Webflow data to the Designer Extension
1. Authenticate the Session Token
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
This endpoint will use jwt.authenticateSessionToken middleware to verifiy the session token passed with the request, ensuring that only authorized users can access this endpoint. Once authenticated, the middleware retrieves the user's associated accessToken and attaches it to the request.
2. Access Webflow Data in the Data Client
server.js
_1
The Data Client then uses the accessToken attached to the request to initialize the Webflow client and fetch data from Webflow’s List Sites endpoint.
3. Return Webflow Data to the Designer Extension
main.js
server.js
database.js
jwt.js
.env
_128
const express = require("express");
_128
const cors = require("cors");
_128
const { WebflowClient } = require("webflow-api");
_128
const axios = require("axios");
_128
require("dotenv").config();
_128
_128
const app = express(); // Create an Express application
_128
const db = require("./database.js"); // Load DB Logic
_128
const jwt = require("./jwt.js")
_128
_128
var corsOptions = { origin: ["http://localhost:1337"] };
_128
_128
// Middleware
_128
app.use(cors(corsOptions)); // Enable CORS with the specified options
res.status(500).json({ error: "Internal server error" });
_128
}
_128
});
_128
_128
_128
// Start the server
_128
const PORT = process.env.PORT || 3000;
_128
app.listen(PORT, () => {
_128
console.log(`Server is running on http://localhost:${PORT}`);
_128
});
After retrieving the data, the endpoint responds with the information, providing the user access to their Webflow sites in the Designer Extension.
Start your App and test your authentication flow
You're all set up to securely make calls to the Webflow Data API from a Designer Extension. To test out your App, run the following command to get started:
$ npm run dev
Once your App is up and running:
Set Your Callback URI: Update the Webflow dashboard with the correct callback URI to ensure the OAuth flow can return to your app after authentication.
Open your App in the Designer: Open a site in the Designer and open your test App with the corresponding CLIENT_ID
Authorize in the Designer Extension: Open your Designer Extension and click the "Authorize" button to initiate the authentication flow.
Congratulations!
You have a working Hybrid app that can securely make requests to Webflow’s Data APIs.
Transmits a Site ID and ID Token
Send a Site ID and ID Token from the Designer Extension to the Data Client for verification.
Resolves the ID Token on the Server Side
The Data Client resolves the ID Token to get user details from Webflow and verify access.
Maps user authorization
Establish a link between the user and their authorized sites, allowing for secure access to site data.
Creates a Session Token for the user
Generate and send a session token to the Designer Extension to securely maintain the user’s session and enable authenticated requests.
Makes authenticated requests to Webflow’s Data APIs
Use the session token and stored tokens to make authenticated API calls to Webflow’s Data APIs.
By the end, you’ll have a secure, fully integrated setup to handle user sessions and seamlessly make requests to external APIs.
Prerequisites
A Hybrid App with the following scopes: sites:read, authorized_user:read
Otherwise, you can clone the repository from GitHub.com
Install dependencies
This example contains both a Designer Extension and Data Client project. Input the following commands in your terminal to install all necessary dependencies for the example.
$ cd hybrid-app-authentication
npm install
npm run install-frontend
npm run install-backend
Add environment variables
Replace the example values in the .env.example file with your credentials. Rename the file to .env
Review Designer Extension
In this tutorial, most of our focus will be on setting up and configuring the Data Client. However, to kick off the authentication process, we need to first send an ID token and Site ID from the Designer Extension to the Data Client. This step will allow us to exchange these tokens for a session token, which is required for making authenticated requests to Webflow’s API from the browser.
To get started, review the exchangeAndVerifyIdToken function in the Designer Extension.
Retrieving the idToken from the Designer Extension.
The exchangeAndVerifyIdToken function initiates the authentication process by using the Webflow Designer API’s getIdToken and getSiteInfo methods to retrieve the idToken and siteId from the Designer Extension.
Exchanging the idToken and siteId for a Session Token
With the ID token and Site ID in hand, the Designer Extension then sends this information to an endpoint on the Data Client.
The Data Client verifies the ID token and returns a session token, which the Designer Extension will use to make authenticated requests to Webflow. This token ensures secure, temporary access and allows the Designer Extension to manage user sessions as we continue through the setup.
Set up your server
Switching to the Data Client, let’s quickly set up an Express server to handle incoming requests and prepare for adding authentication in Data Client/server.js.
Initialize Express
Create your server with Express, configure CORS to accept incoming requests from your Designer, and set up middleware for JSON and URL-encoded requests
Configure authorization flow
Add an endpoint on your server to handle the OAuth callback and retrieve an authorization_code from the URI’s query parameters. Then, create a /callback endpoint to exchange the code for an accessToken for your user.
Handling the ID Token and Site ID from the Designer Extension
In addition to retrieving an accessToken through OAuth, your Designer Extension will also receive an idToken and a siteId during the authentication process for the Designer Extension. These tokens enable you to verify and authorize user access securely.
To manage this, you’ll need to set up a new endpoint that:
Retrieves an accessToken associated with the Site ID from a database
Generates a Session Token for secure, temporary access.
Stores the accessToken in your database, associating it with the user or site.
Sends the Session Token to the Designer Extension
For now, we’ll set up the endpoint to receive the ID Token. We’ll add the database storage, access token retrieval, and session token generation in the next steps.
Set up your database to save and retrieve credentials
Now you've set up your server to retreive an access token, let’s configure a database to store user credentials and authorization details in database.js.
Set up the database schema and tables
First, you’ll create the necessary database schema to store site and user authorizations. This includes two main tables to associate site IDs and user IDs with their respective access tokens.
Store authorization data
Next, you’ll store the Site IDs and user access tokens in the database using functions that insert new records if they don’t already exist.
Store Site Authorization Data with insertSiteAuthorization
Use this function to pair a siteId with its access token when a site is initially authorized.
Store User Authorization Data with insertUserAuthorization
Use this function to pair a userId and access token.
Retrieve authorization data
Finally, you’ll retrieve the stored access tokens using functions designed to fetch access tokens based on siteId or userId.
Retrieve Site Access Tokens with getAccessTokenFromSiteId
This function retrieves the access token for a specific siteId. We'll use this function to obtain user details from Webflow's Resolve ID Token endpoint when a Designer Extension sends an idToken and siteId to our /token endpoint.
Retrieve User Access Tokens with getAccessTokenFromUserId
Use this function to get a user-specific access token, enabling authenticated access to the Webflow API based on the userId.
Export functions
Once these functions are ready, export them along with the database connection.
Configure authorization flow with token storage
To handle authorization effectively, you’ll need to store access tokens securely.
Import the database module into server.js
Exchange Aauthorization Code for Access Token and store it in the database
Update the /callback endpoint in server.js to exchange the code for an accessToken.
To get a list of Sites that the App is authorized to access, instatiate the WebflowClient and call the List Sites endpoint. For each authorized site, use the db.insertSiteAuthorization function to store the siteId and corresponding accessToken in the database for secure, future access.
Redirect the User to the Designer Extension using a deep link
This link allows the user to seamlessly continue within your App in the Webflow Designer. For now, the example below redirects to the first available site, but you could enhance the UX by allowing users to select a specific site before redirecting.
Integrate JWT for secure session management
With our database configured to handle access tokens, it’s time to implement JSON Web Tokens (JWT) to securely manage sessions and authenticate requests in the Data Client.
JWT will enable us to issue session tokens and validate them, allowing for a secure, stateless authentication flow.
Setting up JWT middleware
In jwt.js, start by importing the jsonwebtoken library and your custom database module. These imports will allow you to handle JWT creation and validation, as well as access and store authorization data in your database.
Retrieve the Access Token based on the Site ID
Create the retrieveAccessToken function to obtain the Access Token associated with a given siteId using the db.getAccssTokenFromSiteId function we created in our database module.
When the Designer Extension passes a siteId and idToken to our /token endpoint in the Data Client, we'll use this middleware to retreive the Access Token associated with the siteId and attach it to the request object. This way, subsequent steps in the endpoint have access to the token, allowing the Data Client to securely interact with Webflow’s API.
Create the Session Token from User Data
The createSessionToken function generates a session token using JWT based on user data from the resolved idToken.
This sessionToken, which includes the user’s details, will later be verified for secure access. The token is set to expire after a defined time (in this example, 24 hours), ensuring that the session remains secure and temporary.
Authenticate the Session Token to Validate User Access on Each Request
The authenticateSessionToken function is middleware that validates the sessionToken provided in the authorization header of each request. By verifying the sessionToken, this function ensures that only authorized users can access protected routes. Upon successful validation, it retrieves the accessToken for the user and attaches it to the request, allowing further secure interactions with Webflow.
Export these functions once they are ready.
Using JWT middleware in the server
Once the JWT functions are set up, the next step is to import them into server.js to securely manage session tokens and authenticate requests.
Import JWT Middleware Functions
At the beginning of server.js, import the the JWT middleware.
Update the /token endpoint to authenticate the Designer Extension user
Now that you’ve set up the JWT and database functions, you can update the /token endpoint in server.js to securely authenticate the user. This endpoint will:
Retrieve the accessToken associated with the user's siteId
Get user details from the resolved idToken
Generate a Session Token from user details
Store the authorization details
See the steps below for more detail.
1. Retrieve the accessToken associated with the user's siteId
The jwt.retrieveAccessToken middleware fetches the accessToken associated with the siteId and attaches it to the request, ensuring secure access for the Webflow API call.
2. Get user details from the resolved idToken
Using the accessToken, this endpoint sends an authenticated request to Webflow’s Resolve ID Token endpoint, which validates the idToken received from the Designer Extension, and returns the following user details: id, email, firstName, and lastName.
3. Generate a Session Token
With the newly obtained user information, the jwt.createSessionToken function creates a Session Token for the authenticated user, establishing a secure, temporary access session.
4. Store Authorization Details
Finally, the retrieved accessToken is stored in the database and associated with the userId using db.insertUserAuthorization, enabling easy access and authorization for subsequent requests
Create a protected endpoint for authenticated Webflow requests
To demonstrate how to make secure, protected requests, let's set up an endpoint that requires a Session Token for access. We'll create a new /sites endpoint that will:
Authenticate the Session Token
Access Webflow data in the Data Client
Return Webflow data to the Designer Extension
1. Authenticate the Session Token
This endpoint will use jwt.authenticateSessionToken middleware to verifiy the session token passed with the request, ensuring that only authorized users can access this endpoint. Once authenticated, the middleware retrieves the user's associated accessToken and attaches it to the request.
2. Access Webflow Data in the Data Client
server.js
_1
The Data Client then uses the accessToken attached to the request to initialize the Webflow client and fetch data from Webflow’s List Sites endpoint.
3. Return Webflow Data to the Designer Extension
After retrieving the data, the endpoint responds with the information, providing the user access to their Webflow sites in the Designer Extension.
Start your App and test your authentication flow
You're all set up to securely make calls to the Webflow Data API from a Designer Extension. To test out your App, run the following command to get started:
$ npm run dev
Once your App is up and running:
Set Your Callback URI: Update the Webflow dashboard with the correct callback URI to ensure the OAuth flow can return to your app after authentication.
Open your App in the Designer: Open a site in the Designer and open your test App with the corresponding CLIENT_ID
Authorize in the Designer Extension: Open your Designer Extension and click the "Authorize" button to initiate the authentication flow.
Congratulations!
You have a working Hybrid app that can securely make requests to Webflow’s Data APIs.