Securely connect your browser-based Designer Extension to your server-side Data Client.

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

  • Authorizes sites upon App installation
  • 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
  • The CLIENT_ID and CLIENT_SECRET for your App
  • An ngrok authentication token
  • An understanding of how to authenticate a Webflow User
  • Basic knowledge of Node.js and Express
  • Familiarity with building React Single-Page Applications

Set up your development environment

Clone the starter code

If you have the GitHub CLI installed, type the following command into your terminal.

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

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.

For an in-depth explanation on setting up auth, check out the guide

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:

  1. Validates the ID Token received from the Designer Extension using the Resolve ID Token endpoint.
  2. Retrieves an accessToken associated with the Site ID from a database
  3. Generates a Session Token for secure, temporary access.
  4. Stores the accessToken in your database, associating it with the user or site.
  5. 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.

  1. Store Site Authorization Data with insertSiteAuthorization
    Use this function to pair a siteId with its access token when a site is initially authorized.
  2. 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.

  1. 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.
  2. 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:

  1. Retrieve the accessToken associated with the user's siteId
  2. Get user details from the resolved idToken
  3. Generate a Session Token from user details
  4. 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:

  1. Authenticate the Session Token
  2. Access Webflow data in the Data Client
  3. 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:

  1. 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.
  2. Open your App in the Designer: Open a site in the Designer and open your test App with the corresponding CLIENT_ID
  3. 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.

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.

main.js
server.js
database.js
jwt.js
ExpandClose
1
import React, { useState, useEffect } from "react";
2
import ReactDOM from "react-dom/client";
3
import axios from "axios";
4
import DataTable from "./DataTable";
5
import { ThemeProvider, Button, Container, Typography } from "@mui/material";
6
import theme from "./theme";
7

8
const App = () => {
9
const [idToken, setIdToken] = useState("");
10
const [sessionToken, setSessionToken] = useState("");
11
const [user, setUser] = useState({});
12
const [siteData, setSiteData] = useState([]);
13

14
const PORT = 3000;
15
const API_URL = `http://localhost:${PORT}/`;
16

17
useEffect(() => {
18
// Set Extension Size
19
webflow.setExtensionSize("default");
20

21
// Function to exchange and verify ID token
22
const exchangeAndVerifyIdToken = async () => {
23
try {
24
const idToken = await webflow.getIdToken();
25
const siteInfo = await webflow.getSiteInfo();
26
setIdToken(idToken);
27

28
// Resolve token by sending it to the backend server
29
const response = await axios.post(API_URL + "token", {
30
idToken: idToken,
31
siteId: siteInfo.siteId,
32
});
33

34
try {
35
// Parse information from resolved token
36
const sessionToken = response.data.sessionToken;
37
const expAt = response.data.exp;
38
const decodedToken = JSON.parse(atob(sessionToken.split(".")[1]));
39
const firstName = decodedToken.user.firstName;
40
const email = decodedToken.user.email;
41

42
// Store information in Local Storage
43
localStorage.setItem(
44
"wf_hybrid_user",
45
JSON.stringify({ sessionToken, firstName, email, exp: expAt })
46
);
47
setUser({ firstName, email });
48
setSessionToken(sessionToken);
49
console.log(`Session Token: ${sessionToken}`);
50
} catch (error) {
51
console.error("No Token", error);
52
}
53
} catch (error) {
54
console.error("Error fetching ID Token:", error);
55
}
56
};
57

58
// Check local storage for session token
59
const localStorageUser = localStorage.getItem("wf_hybrid_user");
60
if (localStorageUser) {
61
const userParse = JSON.parse(localStorageUser);
62
const userStoredSessionToken = userParse.sessionToken;
63
const userStoredTokenExp = userParse.exp;
64
if (userStoredSessionToken && Date.now() < userStoredTokenExp) {
65
if (!sessionToken) {
66
setSessionToken(userStoredSessionToken);
67
setUser({ firstName: userParse.firstName, email: userParse.email });
68
}
69
} else {
70
localStorage.removeItem("wf_hybrid_user");
71
exchangeAndVerifyIdToken();
72
}
73
} else {
74
exchangeAndVerifyIdToken();
75
}
76

77
// Listen for message from the OAuth callback window
78
const handleAuthComplete = (event) => {
79
if (
80
// event.origin === "http://localhost:3000" &&
81
event.data === "authComplete"
82
) {
83
exchangeAndVerifyIdToken(); // Retry the token exchange
84
}
85
};
86

87
window.addEventListener("message", handleAuthComplete);
88

89
return () => {
90
window.removeEventListener("message", handleAuthComplete);
91
};
92
}, [sessionToken]);
93

94
// Handle request for site data
95
const getSiteData = async () => {
96
const sites = await axios.get(API_URL + "sites", {
97
headers: { authorization: `Bearer ${sessionToken}` },
98
});
99
setSiteData(sites.data.data.sites);
100
};
101

102
// Open OAuth screen
103
const openAuthScreen = () => {
104
window.open("http://localhost:3000", "_blank", "width=600,height=400");
105
};
106

107
return (
108
<ThemeProvider theme={theme}>
109
<div>
110
{!user.firstName ? (
111
// If no user is found, Send a Hello Stranger Message and Button to Authorize
112
<Container sx={{ padding: "20px" }}>
113
<Typography variant="h1">👋🏾 Hello Stranger</Typography>
114
<Button
115
variant="contained"
116
sx={{ margin: "10px 20px" }}
117
onClick={openAuthScreen}
118
>
119
Authorize App
120
</Button>
121
</Container>
122
) : (
123
// If a user is found send welcome message with their name
124
<Container sx={{ padding: "20px" }}>
125
<Typography variant="h1">👋🏾 Hello {user.firstName}</Typography>
126
<Button
127
variant="contained"
128
sx={{ margin: "10px 20px" }}
129
onClick={getSiteData}
130
>
131
Get Sites
132
</Button>
133
{siteData.length > 0 && <DataTable data={siteData} />}
134
</Container>
135
)}
136
</div>
137
</ThemeProvider>
138
);
139
};
140

141
// Render your App component inside the root
142
const rootElement = document.getElementById("root");
143
const root = ReactDOM.createRoot(rootElement);
144

145
root.render(<App />);