jwt.js
server.js
main.js
database.js

_39
const jwt = require('jsonwebtoken')
_39
const db = require('./database.js')
_39
_39
const createSessionToken = (user) => {
_39
const sessionToken = jwt.sign({ user }, process.env.WEBFLOW_CLIENT_SECRET, { expiresIn: '24h' }); // Example expiration time of 1 hour}
_39
return sessionToken
_39
}
_39
// Middleware to authenticate and validate JWT, and fetch the decrypted access token
_39
const authenticateToken = (req, res, next) => {
_39
const authHeader = req.headers.authorization;
_39
const token = authHeader && authHeader.split(' ')[1]; // Extract the token from 'Bearer <token>'
_39
if (!token) {
_39
return res.status(401).json({ message: 'Authentication token is missing' });
_39
}
_39
_39
// Verify the Token
_39
jwt.verify(token, process.env.WEBFLOW_CLIENT_SECRET, (err, user) => {
_39
if (err) {
_39
return res.status(403).json({ message: 'Invalid or expired token' });
_39
}
_39
_39
// Use the user details to fetch the access token from the database
_39
db.getAccessToken(user.user, (error, accessToken) => {
_39
_39
if (error) {
_39
return res.status(500).json({ error: 'Failed to retrieve access token' });
_39
}
_39
// Attach access token in the request object so that you can make an authenticated request to Webflow
_39
req.accessToken = accessToken;
_39
_39
next(); // Proceed to next middleware or route handler
_39
});
_39
});
_39
};
_39
_39
module.exports = {
_39
createSessionToken,
_39
authenticateToken
_39
}

Learn how to establish a secure connection between your Designer Extension and Data Client. The provided client- and server- side code uses JSON Web Tokens (JWTs) to make authenticated requests without compromising client credentials. Before you get started, make sure your App is configured to use both the Data Extension and Data Client capabilities.

In this tutorial we’ll build a Hybrid App that:

  • Generates and resolves a user’s ID Token for authentication
  • Creates a session token (JWT) from user data
  • Makes authenticated requests to Webflow’s Data APIs.

Prerequisites

Make sure you have a Hybrid App with the sites:read and authorized_user:read scopes, as well as a bearer key generated from your app to use an App token. Additionally, you should have basic knowledge of Node.js and Express and familiarity with building React Single-Page Applications.

Set up your development environment

Clone the starter code

Clone an example Designer Extension and Data Client designed to give you a solid foundation. In this tutorial, you will be guided through adding specific code examples.

$ gh repo clone Webflow-Examples/Hybrid-App-Authentication

Install dependencies and add environment variables

Install the necessary dependencies, and configure environment variables for your App’s Client ID and secret.

$ npm install

Add JWT Middleware

Add logic for minting session tokens

jwt.js
server.js
main.js
database.js

_39
const jwt = require('jsonwebtoken')
_39
const db = require('./database.js')
_39
_39
const createSessionToken = (user) => {
_39
const sessionToken = jwt.sign({ user }, process.env.WEBFLOW_CLIENT_SECRET, { expiresIn: '24h' }); // Example expiration time of 1 hour}
_39
return sessionToken
_39
}
_39
// Middleware to authenticate and validate JWT, and fetch the decrypted access token
_39
const authenticateToken = (req, res, next) => {
_39
const authHeader = req.headers.authorization;
_39
const token = authHeader && authHeader.split(' ')[1]; // Extract the token from 'Bearer <token>'
_39
if (!token) {
_39
return res.status(401).json({ message: 'Authentication token is missing' });
_39
}
_39
_39
// Verify the Token
_39
jwt.verify(token, process.env.WEBFLOW_CLIENT_SECRET, (err, user) => {
_39
if (err) {
_39
return res.status(403).json({ message: 'Invalid or expired token' });
_39
}
_39
_39
// Use the user details to fetch the access token from the database
_39
db.getAccessToken(user.user, (error, accessToken) => {
_39
_39
if (error) {
_39
return res.status(500).json({ error: 'Failed to retrieve access token' });
_39
}
_39
// Attach access token in the request object so that you can make an authenticated request to Webflow
_39
req.accessToken = accessToken;
_39
_39
next(); // Proceed to next middleware or route handler
_39
});
_39
});
_39
};
_39
_39
module.exports = {
_39
createSessionToken,
_39
authenticateToken
_39
}

Add the jsonwebtoken package to handle token generation and verification, as well as the database module to access user information. Then, create a function to mint a session token using your client secret.

Establish request verification for protected endpoints

jwt.js
server.js
main.js
database.js

_39
const jwt = require('jsonwebtoken')
_39
const db = require('./database.js')
_39
_39
const createSessionToken = (user) => {
_39
const sessionToken = jwt.sign({ user }, process.env.WEBFLOW_CLIENT_SECRET, { expiresIn: '24h' }); // Example expiration time of 1 hour}
_39
return sessionToken
_39
}
_39
// Middleware to authenticate and validate JWT, and fetch the decrypted access token
_39
const authenticateToken = (req, res, next) => {
_39
const authHeader = req.headers.authorization;
_39
const token = authHeader && authHeader.split(' ')[1]; // Extract the token from 'Bearer <token>'
_39
if (!token) {
_39
return res.status(401).json({ message: 'Authentication token is missing' });
_39
}
_39
_39
// Verify the Token
_39
jwt.verify(token, process.env.WEBFLOW_CLIENT_SECRET, (err, user) => {
_39
if (err) {
_39
return res.status(403).json({ message: 'Invalid or expired token' });
_39
}
_39
_39
// Use the user details to fetch the access token from the database
_39
db.getAccessToken(user.user, (error, accessToken) => {
_39
_39
if (error) {
_39
return res.status(500).json({ error: 'Failed to retrieve access token' });
_39
}
_39
// Attach access token in the request object so that you can make an authenticated request to Webflow
_39
req.accessToken = accessToken;
_39
_39
next(); // Proceed to next middleware or route handler
_39
});
_39
});
_39
};
_39
_39
module.exports = {
_39
createSessionToken,
_39
authenticateToken
_39
}

Create a middleware function that verifies incoming requests by retrieving the sessionToken from a request’s authorization header and verifying it. Once verified, search for a user’s accessToken in the database, and return it to use in authenticated requests.

Set up the Data Client

Configure authorization flow

jwt.js
server.js
main.js
database.js

_104
const express = require("express");
_104
const cors = require("cors");
_104
const { WebflowClient } = require("webflow-api");
_104
const axios = require("axios");
_104
require("dotenv").config();
_104
_104
const app = express(); // Create an Express application
_104
const db = require("./database.js"); // Load DB Logic
_104
const jwt = require("./jwt.js")
_104
_104
var corsOptions = { origin: ["http://localhost:1337"] };
_104
_104
// Middleware
_104
app.use(cors(corsOptions)); // Enable CORS with the specified options
_104
app.use(express.json()); // Parse JSON-formatted incoming requests
_104
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded incoming requests with extended syntax
_104
_104
// Redirect user to Webflow Authorization screen
_104
app.get("/authorize", (req, res) => {
_104
_104
const authorizeUrl = WebflowClient.authorizeURL({
_104
scope: ["sites:read","authorized_user:read"],
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
})
_104
res.redirect(authorizeUrl)
_104
})
_104
_104
// Optional: Redirect root to Webflow Authorization screen
_104
app.get("/", (req,res) =>{
_104
res.redirect("/authorize")
_104
})
_104
_104
// Exchange the authorization code for an access token and save to DB
_104
app.get("/callback", async (req, res) => {
_104
const { code } = req.query;
_104
_104
// Get Access Token
_104
const accessToken = await WebflowClient.getAccessToken({
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
clientSecret: process.env.WEBFLOW_CLIENT_SECRET,
_104
code: code,
_104
});
_104
_104
// Instantiate the Webflow Client
_104
const webflow = new WebflowClient({ accessToken });
_104
_104
// Get Authorization Details
_104
const user = await webflow.token.authorizedBy();
_104
user.accessToken = accessToken; // add access token to user object
_104
_104
// Save User Details to DB
_104
db.insertAuthorization(user);
_104
});
_104
_104
// Authenticate Designer Extension User via ID Token
_104
app.post("/token", async (req, res) => {
_104
const token = req.body.idToken; // Get token from request
_104
_104
// Resolve Session token by makeing a Request to Webflow API
_104
const APP_TOKEN = process.env.APP_TOKEN;
_104
const options = {
_104
method: "POST",
_104
url: "https://api.webflow.com/beta/token/resolve",
_104
headers: {
_104
accept: "application/json",
_104
"content-type": "application/json",
_104
authorization: `Bearer ${process.env.APP_TOKEN}`,
_104
},
_104
data: {
_104
idToken: token,
_104
},
_104
};
_104
const request = await axios.request(options);
_104
const user = request.data;
_104
_104
// Generate a Session Token
_104
const sessionToken = jwt.createSessionToken(user)
_104
_104
// Respond to user with sesion token
_104
res.json({ sessionToken });
_104
});
_104
_104
// Make authenticated request with user's session token
_104
app.get("/sites", jwt.authenticateToken ,async (req, res) => {
_104
_104
try {
_104
// Initialize Webflow Client and make request to sites endpoint
_104
const accessToken = req.accessToken
_104
const webflow = new WebflowClient({ accessToken });
_104
const data = await webflow.sites.list();
_104
console.log(accessToken)
_104
// Send the retrieved data back to the client
_104
res.json({ data });
_104
} catch (error) {
_104
console.error("Error handling authenticated request:", error);
_104
res.status(500).json({ error: "Internal server error" });
_104
}
_104
});
_104
_104
// Start the server
_104
const PORT = process.env.PORT || 3000;
_104
app.listen(PORT, () => {
_104
console.log(`Server is running on http://localhost:${PORT}`);
_104
});

Add an endpoint on your server that retrieves an authorization_code from your OAuth callback URI. Use the code in the URI’s query parameter to request an accessToken for your user. Instantiate the Webflow client, and send a request to get your user’s details. Save the user details and accessToken to the database.

Get user details from an ID token

jwt.js
server.js
main.js
database.js

_104
const express = require("express");
_104
const cors = require("cors");
_104
const { WebflowClient } = require("webflow-api");
_104
const axios = require("axios");
_104
require("dotenv").config();
_104
_104
const app = express(); // Create an Express application
_104
const db = require("./database.js"); // Load DB Logic
_104
const jwt = require("./jwt.js")
_104
_104
var corsOptions = { origin: ["http://localhost:1337"] };
_104
_104
// Middleware
_104
app.use(cors(corsOptions)); // Enable CORS with the specified options
_104
app.use(express.json()); // Parse JSON-formatted incoming requests
_104
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded incoming requests with extended syntax
_104
_104
// Redirect user to Webflow Authorization screen
_104
app.get("/authorize", (req, res) => {
_104
_104
const authorizeUrl = WebflowClient.authorizeURL({
_104
scope: ["sites:read","authorized_user:read"],
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
})
_104
res.redirect(authorizeUrl)
_104
})
_104
_104
// Optional: Redirect root to Webflow Authorization screen
_104
app.get("/", (req,res) =>{
_104
res.redirect("/authorize")
_104
})
_104
_104
// Exchange the authorization code for an access token and save to DB
_104
app.get("/callback", async (req, res) => {
_104
const { code } = req.query;
_104
_104
// Get Access Token
_104
const accessToken = await WebflowClient.getAccessToken({
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
clientSecret: process.env.WEBFLOW_CLIENT_SECRET,
_104
code: code,
_104
});
_104
_104
// Instantiate the Webflow Client
_104
const webflow = new WebflowClient({ accessToken });
_104
_104
// Get Authorization Details
_104
const user = await webflow.token.authorizedBy();
_104
user.accessToken = accessToken; // add access token to user object
_104
_104
// Save User Details to DB
_104
db.insertAuthorization(user);
_104
});
_104
_104
// Authenticate Designer Extension User via ID Token
_104
app.post("/token", async (req, res) => {
_104
const token = req.body.idToken; // Get token from request
_104
_104
// Resolve Session token by makeing a Request to Webflow API
_104
const APP_TOKEN = process.env.APP_TOKEN;
_104
const options = {
_104
method: "POST",
_104
url: "https://api.webflow.com/beta/token/resolve",
_104
headers: {
_104
accept: "application/json",
_104
"content-type": "application/json",
_104
authorization: `Bearer ${process.env.APP_TOKEN}`,
_104
},
_104
data: {
_104
idToken: token,
_104
},
_104
};
_104
const request = await axios.request(options);
_104
const user = request.data;
_104
_104
// Generate a Session Token
_104
const sessionToken = jwt.createSessionToken(user)
_104
_104
// Respond to user with sesion token
_104
res.json({ sessionToken });
_104
});
_104
_104
// Make authenticated request with user's session token
_104
app.get("/sites", jwt.authenticateToken ,async (req, res) => {
_104
_104
try {
_104
// Initialize Webflow Client and make request to sites endpoint
_104
const accessToken = req.accessToken
_104
const webflow = new WebflowClient({ accessToken });
_104
const data = await webflow.sites.list();
_104
console.log(accessToken)
_104
// Send the retrieved data back to the client
_104
res.json({ data });
_104
} catch (error) {
_104
console.error("Error handling authenticated request:", error);
_104
res.status(500).json({ error: "Internal server error" });
_104
}
_104
});
_104
_104
// Start the server
_104
const PORT = process.env.PORT || 3000;
_104
app.listen(PORT, () => {
_104
console.log(`Server is running on http://localhost:${PORT}`);
_104
});

Create an endpoint on your server that accepts an idToken. Within this endpoint, send a request to Webflow’s resolve token endpoint, which will return details about the user that requested the idToken. Use these details to query the database for an authorized user. Be sure to authenticate the resolve token request with your appToken and include the idToken in the request body.

Mint a session token and return it to the Designer Extension

jwt.js
server.js
main.js
database.js

_104
const express = require("express");
_104
const cors = require("cors");
_104
const { WebflowClient } = require("webflow-api");
_104
const axios = require("axios");
_104
require("dotenv").config();
_104
_104
const app = express(); // Create an Express application
_104
const db = require("./database.js"); // Load DB Logic
_104
const jwt = require("./jwt.js")
_104
_104
var corsOptions = { origin: ["http://localhost:1337"] };
_104
_104
// Middleware
_104
app.use(cors(corsOptions)); // Enable CORS with the specified options
_104
app.use(express.json()); // Parse JSON-formatted incoming requests
_104
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded incoming requests with extended syntax
_104
_104
// Redirect user to Webflow Authorization screen
_104
app.get("/authorize", (req, res) => {
_104
_104
const authorizeUrl = WebflowClient.authorizeURL({
_104
scope: ["sites:read","authorized_user:read"],
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
})
_104
res.redirect(authorizeUrl)
_104
})
_104
_104
// Optional: Redirect root to Webflow Authorization screen
_104
app.get("/", (req,res) =>{
_104
res.redirect("/authorize")
_104
})
_104
_104
// Exchange the authorization code for an access token and save to DB
_104
app.get("/callback", async (req, res) => {
_104
const { code } = req.query;
_104
_104
// Get Access Token
_104
const accessToken = await WebflowClient.getAccessToken({
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
clientSecret: process.env.WEBFLOW_CLIENT_SECRET,
_104
code: code,
_104
});
_104
_104
// Instantiate the Webflow Client
_104
const webflow = new WebflowClient({ accessToken });
_104
_104
// Get Authorization Details
_104
const user = await webflow.token.authorizedBy();
_104
user.accessToken = accessToken; // add access token to user object
_104
_104
// Save User Details to DB
_104
db.insertAuthorization(user);
_104
});
_104
_104
// Authenticate Designer Extension User via ID Token
_104
app.post("/token", async (req, res) => {
_104
const token = req.body.idToken; // Get token from request
_104
_104
// Resolve Session token by makeing a Request to Webflow API
_104
const APP_TOKEN = process.env.APP_TOKEN;
_104
const options = {
_104
method: "POST",
_104
url: "https://api.webflow.com/beta/token/resolve",
_104
headers: {
_104
accept: "application/json",
_104
"content-type": "application/json",
_104
authorization: `Bearer ${process.env.APP_TOKEN}`,
_104
},
_104
data: {
_104
idToken: token,
_104
},
_104
};
_104
const request = await axios.request(options);
_104
const user = request.data;
_104
_104
// Generate a Session Token
_104
const sessionToken = jwt.createSessionToken(user)
_104
_104
// Respond to user with sesion token
_104
res.json({ sessionToken });
_104
});
_104
_104
// Make authenticated request with user's session token
_104
app.get("/sites", jwt.authenticateToken ,async (req, res) => {
_104
_104
try {
_104
// Initialize Webflow Client and make request to sites endpoint
_104
const accessToken = req.accessToken
_104
const webflow = new WebflowClient({ accessToken });
_104
const data = await webflow.sites.list();
_104
console.log(accessToken)
_104
// Send the retrieved data back to the client
_104
res.json({ data });
_104
} catch (error) {
_104
console.error("Error handling authenticated request:", error);
_104
res.status(500).json({ error: "Internal server error" });
_104
}
_104
});
_104
_104
// Start the server
_104
const PORT = process.env.PORT || 3000;
_104
app.listen(PORT, () => {
_104
console.log(`Server is running on http://localhost:${PORT}`);
_104
});

Within the same endpoint, use the JWT middleware to mint a sessionToken from the user data. Once generated, return the sessionToken to complete the Designer Extension authentication process.

Create a protected endpoint for authenticated Webflow requests

jwt.js
server.js
main.js
database.js

_104
const express = require("express");
_104
const cors = require("cors");
_104
const { WebflowClient } = require("webflow-api");
_104
const axios = require("axios");
_104
require("dotenv").config();
_104
_104
const app = express(); // Create an Express application
_104
const db = require("./database.js"); // Load DB Logic
_104
const jwt = require("./jwt.js")
_104
_104
var corsOptions = { origin: ["http://localhost:1337"] };
_104
_104
// Middleware
_104
app.use(cors(corsOptions)); // Enable CORS with the specified options
_104
app.use(express.json()); // Parse JSON-formatted incoming requests
_104
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded incoming requests with extended syntax
_104
_104
// Redirect user to Webflow Authorization screen
_104
app.get("/authorize", (req, res) => {
_104
_104
const authorizeUrl = WebflowClient.authorizeURL({
_104
scope: ["sites:read","authorized_user:read"],
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
})
_104
res.redirect(authorizeUrl)
_104
})
_104
_104
// Optional: Redirect root to Webflow Authorization screen
_104
app.get("/", (req,res) =>{
_104
res.redirect("/authorize")
_104
})
_104
_104
// Exchange the authorization code for an access token and save to DB
_104
app.get("/callback", async (req, res) => {
_104
const { code } = req.query;
_104
_104
// Get Access Token
_104
const accessToken = await WebflowClient.getAccessToken({
_104
clientId: process.env.WEBFLOW_CLIENT_ID,
_104
clientSecret: process.env.WEBFLOW_CLIENT_SECRET,
_104
code: code,
_104
});
_104
_104
// Instantiate the Webflow Client
_104
const webflow = new WebflowClient({ accessToken });
_104
_104
// Get Authorization Details
_104
const user = await webflow.token.authorizedBy();
_104
user.accessToken = accessToken; // add access token to user object
_104
_104
// Save User Details to DB
_104
db.insertAuthorization(user);
_104
});
_104
_104
// Authenticate Designer Extension User via ID Token
_104
app.post("/token", async (req, res) => {
_104
const token = req.body.idToken; // Get token from request
_104
_104
// Resolve Session token by makeing a Request to Webflow API
_104
const APP_TOKEN = process.env.APP_TOKEN;
_104
const options = {
_104
method: "POST",
_104
url: "https://api.webflow.com/beta/token/resolve",
_104
headers: {
_104
accept: "application/json",
_104
"content-type": "application/json",
_104
authorization: `Bearer ${process.env.APP_TOKEN}`,
_104
},
_104
data: {
_104
idToken: token,
_104
},
_104
};
_104
const request = await axios.request(options);
_104
const user = request.data;
_104
_104
// Generate a Session Token
_104
const sessionToken = jwt.createSessionToken(user)
_104
_104
// Respond to user with sesion token
_104
res.json({ sessionToken });
_104
});
_104
_104
// Make authenticated request with user's session token
_104
app.get("/sites", jwt.authenticateToken ,async (req, res) => {
_104
_104
try {
_104
// Initialize Webflow Client and make request to sites endpoint
_104
const accessToken = req.accessToken
_104
const webflow = new WebflowClient({ accessToken });
_104
const data = await webflow.sites.list();
_104
console.log(accessToken)
_104
// Send the retrieved data back to the client
_104
res.json({ data });
_104
} catch (error) {
_104
console.error("Error handling authenticated request:", error);
_104
res.status(500).json({ error: "Internal server error" });
_104
}
_104
});
_104
_104
// Start the server
_104
const PORT = process.env.PORT || 3000;
_104
app.listen(PORT, () => {
_104
console.log(`Server is running on http://localhost:${PORT}`);
_104
});

To facilitate requests for Webflow data from the Designer Extension, create an endpoint that uses the JWT middleware to verify the sessionToken and return an accessToken. Make an authenticated request to Webflow with the accessToken and return the data in the response.

Set up the Designer Extension

Create a request for an ID Token

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

To initiate user authentication, call the webflow.requestIdToken() function. This retrieves an idToken for the active user, which is essential for verifying the user's identity. It’s important to note that the idToken is only valid for 15 minutes, so it should be used promptly.

Create a request for sessionToken and save in Local Storage

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

Send the idToken in the body of a POST request to the “/token” endpoint of the Data Client. Upon successful request, the endpoint returns a sessionToken. Decode the session token to get user details like firstName, email , and sessionToken and set their respective state variables. Store the sessionToken in local storage to maintain user session state across the application.

Call the entire Function

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

Get Webflow data via an authenticated request

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

Create a request to the Data Client’s “/sites” endpoint to access Webflow site data. Authenticate the request by including the sessionToken as a bearer token in the Authorization header. Set a siteData state variable to the data from the response.

Personalize the UI

Build initial user greeting

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

Create a section in your Designer Extension that dynamically greets the user, such as "Hello, firstName," using the user's first name obtained from thesessionToken. This greeting should update seamlessly without requiring a page reload.

Create a button to request site data

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

Add a button labeled "Get Site Data." pass the getSiteData handler to the button's onClick prop, so it will trigger a fetch request to the Data Client’s “/sites” endpoint when clicked.

List retrievd site data

jwt.js
server.js
main.js
database.js

_97
import React, { useState, useEffect } from "react";
_97
import ReactDOM from "react-dom/client";
_97
import axios from "axios";
_97
import DataTable from "./DataTable";
_97
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
_97
import theme from './theme'
_97
_97
const App = () => {
_97
const [idToken, setIdToken] = useState("");
_97
const [sessionToken, setSessionToken] = useState("");
_97
const [user, setUser] = useState({});
_97
const [siteData, setSiteData] = useState([]);
_97
_97
const PORT = 3000;
_97
const API_URL = `http://localhost:${PORT}/`;
_97
_97
useEffect(() => {
_97
// Set Extension Size
_97
webflow.setExtensionSize("default");
_97
_97
// Fetch ID Token and send it to the Data Client
_97
const exchangeAndVerifyIdToken = async () => {
_97
try {
_97
// Get ID Token from Webflow
_97
const idToken = await webflow.getIdToken();
_97
setIdToken(idToken);
_97
_97
console.log(`idToken: ${idToken}`);
_97
_97
// Send token to Webflow, and wait for a response with a JWT from our Data Client
_97
const getSessionToken = async (idToken) => {
_97
_97
// Send ID Token to the Data Client
_97
const response = await axios.post(API_URL + "token", {
_97
idToken: idToken,
_97
});
_97
_97
try {
_97
// Store sessionToken in Local Storage
_97
const sessionToken = response.data.sessionToken;
_97
_97
// Decode the sessionToken
_97
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
_97
const firstName = decodedToken.user.firstName;
_97
const email = decodedToken.user.email;
_97
_97
localStorage.setItem(
_97
"user",
_97
JSON.stringify({
_97
sessionToken: sessionToken,
_97
firstName: firstName,
_97
email: email,
_97
})
_97
);
_97
setUser({ firstName, email });
_97
setSessionToken(sessionToken);
_97
console.log(`Session Token: ${sessionToken}`);
_97
} catch (error) {
_97
console.error("No Token", error);
_97
}
_97
};
_97
await getSessionToken(idToken);
_97
} catch (error) {
_97
console.error("Error fetching ID Token:", error);
_97
}
_97
};
_97
_97
// Run function
_97
exchangeAndVerifyIdToken();
_97
}, []);
_97
_97
// Handle request for site data
_97
const getSiteData = async () => {
_97
const sites = await axios.get(API_URL + "sites", {
_97
headers: { authorization: `Bearer ${sessionToken}` },
_97
});
_97
setSiteData(sites.data.data.sites);
_97
};
_97
_97
return (
_97
<ThemeProvider theme={theme}>
_97
<div>
_97
<Container sx={{padding:'20px'}}>
_97
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
_97
<Button variant="contained" sx={{ margin: '10px 20px' }} onClick={getSiteData}>Get Sites</Button>
_97
{siteData.length > 0 && <DataTable data={siteData} />}
_97
</Container>
_97
</div>
_97
</ThemeProvider>
_97
);
_97
};
_97
_97
// Render your App component inside the root
_97
const rootElement = document.getElementById("root");
_97
const root = ReactDOM.createRoot(rootElement);
_97
_97
root.render(<App />);

To render a list of sites, use the TableData component to dynamically list the site data once it is retrieved, and pass the siteData state variable as prop.

Congratulations!

You have a working Hybrid app that can securely make requests to Webflow’s Data APIs.

Next steps

Add Elements to the Canvas in bulk

Create and manpulate complex hierarchical designs using the ElementBuilder APIs.

Add Custom Code to a Site

Learn how to register, apply, and manage custom scripts on a site or page using the Data APIs.

Publish your Hybrid App

Once you've finished developing your app, discover how to publish it and share it in the marketplace.

Learn how to establish a secure connection between your Designer Extension and Data Client. The provided client- and server- side code uses JSON Web Tokens (JWTs) to make authenticated requests without compromising client credentials. Before you get started, make sure your App is configured to use both the Data Extension and Data Client capabilities.

In this tutorial we’ll build a Hybrid App that:

  • Generates and resolves a user’s ID Token for authentication
  • Creates a session token (JWT) from user data
  • Makes authenticated requests to Webflow’s Data APIs.

Prerequisites

Make sure you have a Hybrid App with the sites:read and authorized_user:read scopes, as well as a bearer key generated from your app to use an App token. Additionally, you should have basic knowledge of Node.js and Express and familiarity with building React Single-Page Applications.

Set up your development environment

Clone the starter code

Clone an example Designer Extension and Data Client designed to give you a solid foundation. In this tutorial, you will be guided through adding specific code examples.

$ gh repo clone Webflow-Examples/Hybrid-App-Authentication

Install dependencies and add environment variables

Install the necessary dependencies, and configure environment variables for your App’s Client ID and secret.

$ npm install

Add JWT Middleware

Add logic for minting session tokens

Add the jsonwebtoken package to handle token generation and verification, as well as the database module to access user information. Then, create a function to mint a session token using your client secret.

Establish request verification for protected endpoints

Create a middleware function that verifies incoming requests by retrieving the sessionToken from a request’s authorization header and verifying it. Once verified, search for a user’s accessToken in the database, and return it to use in authenticated requests.

Set up the Data Client

Configure authorization flow

Add an endpoint on your server that retrieves an authorization_code from your OAuth callback URI. Use the code in the URI’s query parameter to request an accessToken for your user. Instantiate the Webflow client, and send a request to get your user’s details. Save the user details and accessToken to the database.

Get user details from an ID token

Create an endpoint on your server that accepts an idToken. Within this endpoint, send a request to Webflow’s resolve token endpoint, which will return details about the user that requested the idToken. Use these details to query the database for an authorized user. Be sure to authenticate the resolve token request with your appToken and include the idToken in the request body.

Mint a session token and return it to the Designer Extension

Within the same endpoint, use the JWT middleware to mint a sessionToken from the user data. Once generated, return the sessionToken to complete the Designer Extension authentication process.

Create a protected endpoint for authenticated Webflow requests

To facilitate requests for Webflow data from the Designer Extension, create an endpoint that uses the JWT middleware to verify the sessionToken and return an accessToken. Make an authenticated request to Webflow with the accessToken and return the data in the response.

Set up the Designer Extension

Create a request for an ID Token

To initiate user authentication, call the webflow.requestIdToken() function. This retrieves an idToken for the active user, which is essential for verifying the user's identity. It’s important to note that the idToken is only valid for 15 minutes, so it should be used promptly.

Create a request for sessionToken and save in Local Storage

Send the idToken in the body of a POST request to the “/token” endpoint of the Data Client. Upon successful request, the endpoint returns a sessionToken. Decode the session token to get user details like firstName, email , and sessionToken and set their respective state variables. Store the sessionToken in local storage to maintain user session state across the application.

Call the entire Function

Get Webflow data via an authenticated request

Create a request to the Data Client’s “/sites” endpoint to access Webflow site data. Authenticate the request by including the sessionToken as a bearer token in the Authorization header. Set a siteData state variable to the data from the response.

Personalize the UI

Build initial user greeting

Create a section in your Designer Extension that dynamically greets the user, such as "Hello, firstName," using the user's first name obtained from thesessionToken. This greeting should update seamlessly without requiring a page reload.

Create a button to request site data

Add a button labeled "Get Site Data." pass the getSiteData handler to the button's onClick prop, so it will trigger a fetch request to the Data Client’s “/sites” endpoint when clicked.

List retrievd site data

To render a list of sites, use the TableData component to dynamically list the site data once it is retrieved, and pass the siteData state variable as prop.

Congratulations!

You have a working Hybrid app that can securely make requests to Webflow’s Data APIs.

Next steps

Add Elements to the Canvas in bulk

Create and manpulate complex hierarchical designs using the ElementBuilder APIs.

Add Custom Code to a Site

Learn how to register, apply, and manage custom scripts on a site or page using the Data APIs.

Publish your Hybrid App

Once you've finished developing your app, discover how to publish it and share it in the marketplace.

jwt.js
server.js
main.js
database.js
ExpandClose

_39
const jwt = require('jsonwebtoken')
_39
const db = require('./database.js')
_39
_39
const createSessionToken = (user) => {
_39
const sessionToken = jwt.sign({ user }, process.env.WEBFLOW_CLIENT_SECRET, { expiresIn: '24h' }); // Example expiration time of 1 hour}
_39
return sessionToken
_39
}
_39
// Middleware to authenticate and validate JWT, and fetch the decrypted access token
_39
const authenticateToken = (req, res, next) => {
_39
const authHeader = req.headers.authorization;
_39
const token = authHeader && authHeader.split(' ')[1]; // Extract the token from 'Bearer <token>'
_39
if (!token) {
_39
return res.status(401).json({ message: 'Authentication token is missing' });
_39
}
_39
_39
// Verify the Token
_39
jwt.verify(token, process.env.WEBFLOW_CLIENT_SECRET, (err, user) => {
_39
if (err) {
_39
return res.status(403).json({ message: 'Invalid or expired token' });
_39
}
_39
_39
// Use the user details to fetch the access token from the database
_39
db.getAccessToken(user.user, (error, accessToken) => {
_39
_39
if (error) {
_39
return res.status(500).json({ error: 'Failed to retrieve access token' });
_39
}
_39
// Attach access token in the request object so that you can make an authenticated request to Webflow
_39
req.accessToken = accessToken;
_39
_39
next(); // Proceed to next middleware or route handler
_39
});
_39
});
_39
};
_39
_39
module.exports = {
_39
createSessionToken,
_39
authenticateToken
_39
}