# How to authenticate users in a Bubble.io application using JWT (JSON Web Tokens)

- Tool: Bubble
- Difficulty: Beginner
- Time required: 20-25 min
- Compatibility: Starter plan+ (backend workflows required)
- Last updated: March 2026

## TL;DR

JSON Web Tokens enable stateless authentication for Bubble apps accessed by external clients like mobile apps or third-party services. This tutorial covers generating JWTs in backend workflows, validating incoming tokens, and securing API endpoints with token-based authentication.

## Overview: JWT Authentication in Bubble

JWT (JSON Web Token) authentication is essential when external applications — mobile apps, third-party services, or other web apps — need to authenticate with your Bubble backend. Unlike Bubble's built-in cookie-based sessions, JWTs are stateless tokens that can be passed in API headers.

## Before you start

- A Bubble app on Starter plan or above
- The Toolbox plugin installed (for JavaScript operations)
- Basic understanding of backend API workflows
- Familiarity with how REST APIs work

## Step-by-step guide

### 1. Understand when to use JWT in Bubble

Bubble's built-in authentication uses browser cookies, which works for web apps but not for mobile apps, IoT devices, or third-party API consumers. JWT solves this by providing a token that the client stores and sends with every request. Use JWT when: external apps need to call your Bubble API, you are building a mobile app that talks to your Bubble backend, or you need stateless authentication for webhooks.

**Expected result:** You understand when JWT is needed versus Bubble's built-in auth.

### 2. Set up a JWT generation backend workflow

Create a backend API workflow called generate-token. Add parameters: user_id (text) and email (text). Use the Toolbox plugin's Server Script action (or a JWT plugin) to create the token. The JWT payload should include the user_id, email, issued_at timestamp, and expiration time. Sign it with a secret key stored in your app's settings or a private API key. Return the token using Return data from API.

```
// JWT payload structure
{
  "sub": "user_unique_id_here",
  "email": "user@example.com",
  "iat": 1711612800,
  "exp": 1711699200,
  "iss": "your-bubble-app"
}
```

> Pro tip: Store your JWT signing secret as a private key in the API Connector or a backend workflow parameter — never expose it in frontend code.

**Expected result:** A backend workflow that generates signed JWTs for authenticated users.

### 3. Create a login endpoint that returns a JWT

Create a backend API workflow called login that accepts email and password parameters. Use the Log the user in action to verify credentials. If successful, call the generate-token workflow with the user's ID and email. Return the JWT token in the API response. External apps call this endpoint, receive the token, and include it in subsequent requests.

**Expected result:** External apps can authenticate by calling the login endpoint and receiving a JWT.

### 4. Validate incoming JWTs on protected endpoints

On your protected backend API workflows, add a parameter called token (text). At the start of the workflow, use a Server Script or JWT plugin to decode and verify the token. Check that: the signature is valid (matches your secret), the token has not expired (exp > current timestamp), and the issuer matches (iss = your app). If validation fails, return an error response.

**Expected result:** Protected API endpoints reject invalid or expired tokens and only process requests with valid JWTs.

### 5. Build a token refresh mechanism

JWTs should have short expiration times (1-24 hours) for security. Create a refresh-token endpoint that accepts a valid (non-expired) JWT and returns a new one with a fresh expiration. The client stores the new token and uses it for subsequent requests. This prevents users from being forced to re-login frequently.

**Expected result:** Clients can refresh their JWT before it expires without re-authenticating.

## Complete code example

File: `Workflow summary`

```text
JWT AUTHENTICATION SUMMARY
===========================

ENDPOINTS:
  POST /api/1.1/wf/login
    Params: email, password
    Returns: { token: "eyJhbGciOiJIUzI1NiI..." }

  POST /api/1.1/wf/refresh-token
    Params: token (current valid JWT)
    Returns: { token: "eyJhbGciOiJIUzI1NiI..." (new) }

  POST /api/1.1/wf/protected-action
    Params: token, ...other params
    Validates token before processing

JWT STRUCTURE:
  Header: { "alg": "HS256", "typ": "JWT" }
  Payload: { "sub": "user_id", "email": "...", "iat": timestamp, "exp": timestamp, "iss": "app-name" }
  Signature: HMACSHA256(header + payload, secret)

VALIDATION FLOW:
  1. Decode token header and payload (base64)
  2. Verify signature matches using secret
  3. Check exp > current timestamp
  4. Check iss matches expected value
  5. Find user by sub (user_id) in database
  6. If all pass → proceed with workflow
  7. If any fail → return error response

SECURITY:
  - Secret key: stored server-side only
  - Token expiry: 1-24 hours
  - Refresh: issue new token before expiry
  - Never log or expose tokens in error messages
```

## Common mistakes

- **Using a weak or hardcoded JWT secret** — A weak secret can be brute-forced, allowing attackers to forge valid tokens Fix: Use a long, random secret (32+ characters) stored securely in your app settings, never in frontend code
- **Setting very long token expiration times** — If a token is stolen, the attacker has access for the entire lifetime of the token Fix: Set expiration to 1-24 hours and implement a token refresh mechanism
- **Not validating the token signature on every request** — Without signature verification, anyone can forge a token with arbitrary claims Fix: Always verify the JWT signature using your secret before trusting the payload data

## Best practices

- Store the JWT signing secret server-side only, never in frontend code
- Set short token expiration times (1-24 hours) and implement refresh
- Validate the signature, expiration, and issuer on every protected request
- Include minimal claims in the JWT payload — do not store sensitive data
- Use HTTPS for all API endpoints to prevent token interception
- Log authentication events for security monitoring

## Frequently asked questions

### When should I use JWT instead of Bubble's built-in auth?

Use JWT when external apps (mobile, IoT, third-party services) need to authenticate with your Bubble API. For standard web app users, Bubble's built-in cookie-based authentication is simpler and sufficient.

### Can I use JWT for Bubble web app users?

You can, but it adds unnecessary complexity. Bubble's built-in auth handles web sessions automatically. JWT is primarily for external API consumers.

### Is there a JWT plugin for Bubble?

Yes. Several plugins handle JWT generation and validation. Alternatively, use the Toolbox plugin's Server Script with a JavaScript JWT library.

### How do I store the JWT on the client side?

Mobile apps typically store JWTs in secure storage (Keychain on iOS, EncryptedSharedPreferences on Android). Web apps can use localStorage or httpOnly cookies.

### What happens when a JWT expires?

The client receives a 401 Unauthorized response. It should then call the refresh-token endpoint to get a new token, or redirect the user to re-authenticate.

### Can RapidDev help with complex authentication systems?

Yes. RapidDev has built enterprise authentication systems including JWT, OAuth2, SSO, and multi-factor authentication in Bubble for apps serving thousands of users.

---

Source: https://www.rapidevelopers.com/bubble-tutorial/authenticate-users-jwt-bubble-app
© RapidDev — https://www.rapidevelopers.com/bubble-tutorial/authenticate-users-jwt-bubble-app
