Skip to content

Commit 0709eaf

Browse files
Saravana Kumar RajendranBharani0012Kabilan-16
authored
Add optional Clerk authentication (#13)
* revert env example to exclude Clerk settings * chore: store Clerk token in cookie (#14) * Wrap logout mutation with Clerk signout (#15) * refactor: move clerk mock mutation to util (#16) * restore env example and clean doc (#17) * docs: move clerk env vars to integration guide (#18) * fix: replace logout import path (#19) * Clerk auth frontend (#22) * refactor: update auth and login pages to signup users * updated imports in clerk_utils.py * modified clerk_auth and login-page.tsx * updated context-wrapper * updated context-wrapper * modified use-post-refresh-access.ts * add new file for clerk constants * modified autologin for clerk auth * add token refresh effect in auth.tsx * Ruff check resolved * Ruff check resolve changes * imported the enum and remove the logs * removed constant file * update imports in login page.tsx * updated imports in index.tsx file * update imports in use get auto login.tsx file * updated imports in use post refersh access.tsx file * Clerk token verify (#28) * added protected paths in login api * added bearer token in header * ruff check fix * update log level for auth, login-pages, use-get-autologin * signup comment modified * Add Clerk auth settings (#30) * Fix logout in docker (#40) * update make file to inject build args in docker * update docker file to load env * modified constant.ts * Update Makefile and Dockerfile to replace LANGFLOW_AUTO_LOGIN with VITE_AUTO_LOGIN * modified constant.ts --------- Co-authored-by: Bharanitharan <109167711+Bharani0012@users.noreply.github.com> Co-authored-by: unknown <kabik5095@gmail.com> Co-authored-by: Kabilan A <147593493+Kabilan-16@users.noreply.github.com> Co-authored-by: Bharani0012 <bharanitharan695@gmail.com>
1 parent b76540a commit 0709eaf

18 files changed

Lines changed: 493 additions & 11 deletions

File tree

.env.clerk.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Clerk authentication
2+
# When true, the frontend uses Clerk for login and sign up
3+
CLERK_AUTH_ENABLED=
4+
5+
# Publishable key for Clerk when authentication is enabled
6+
CLERK_PUBLISHABLE_KEY=

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ DOCKERFILE=docker/build_and_push.Dockerfile
66
DOCKERFILE_BACKEND=docker/build_and_push_backend.Dockerfile
77
DOCKERFILE_FRONTEND=docker/frontend/build_and_push_frontend.Dockerfile
88
DOCKER_COMPOSE=docker_example/docker-compose.yml
9+
10+
VITE_CLERK_AUTH_ENABLED ?= false
11+
VITE_CLERK_PUBLISHABLE_KEY ?=
912
PYTHON_REQUIRED=$(shell grep '^requires-python[[:space:]]*=' pyproject.toml | sed -n 's/.*"\([^"]*\)".*/\1/p')
1013
RED=\033[0;31m
1114
NC=\033[0m # No Color
@@ -333,6 +336,9 @@ dockerfile_build:
333336
@echo 'BUILDING DOCKER IMAGE: ${DOCKERFILE}'
334337
@docker build --rm \
335338
-f ${DOCKERFILE} \
339+
--build-arg VITE_CLERK_AUTH_ENABLED=${VITE_CLERK_AUTH_ENABLED} \
340+
--build-arg VITE_CLERK_PUBLISHABLE_KEY=${VITE_CLERK_PUBLISHABLE_KEY} \
341+
--build-arg VITE_AUTO_LOGIN=$(VITE_AUTO_LOGIN) \
336342
-t langflow:${VERSION} .
337343

338344
dockerfile_build_be: dockerfile_build

docker/build_and_push.Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,17 @@ RUN --mount=type=cache,target=/root/.cache/uv \
4444

4545
COPY ./src /app/src
4646

47+
ARG VITE_AUTO_LOGIN=true
48+
ENV VITE_AUTO_LOGIN=$VITE_AUTO_LOGIN
49+
4750
COPY src/frontend /tmp/src/frontend
4851
WORKDIR /tmp/src/frontend
52+
53+
ARG VITE_CLERK_AUTH_ENABLED=false
54+
ARG VITE_CLERK_PUBLISHABLE_KEY=""
55+
ENV VITE_CLERK_AUTH_ENABLED=$VITE_CLERK_AUTH_ENABLED
56+
ENV VITE_CLERK_PUBLISHABLE_KEY=$VITE_CLERK_PUBLISHABLE_KEY
57+
4958
RUN --mount=type=cache,target=/root/.npm \
5059
npm ci \
5160
&& npm run build \
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Clerk Authentication
3+
slug: /clerk-auth
4+
---
5+
6+
7+
This page summarizes how Langflow handles access token refreshes in different authentication modes and what state changes occur when using Clerk.
8+
9+
## Refresh scenarios when Clerk authentication is disabled
10+
11+
When `CLERK_AUTH_ENABLED` is `false`, Langflow relies on its internal authentication. Tokens are refreshed in these situations:
12+
13+
1. **Protected routes** – the `ProtectedRoute` component periodically calls the `/refresh` endpoint using the `useRefreshAccessToken` hook.
14+
2. **API requests** – the API interceptor automatically triggers the same hook when a request fails with a `401` or `403` error.
15+
16+
Both cases invoke the `useRefreshAccessToken` hook which posts to `/refresh` and updates the refresh token cookie.
17+
The default interval used by `ProtectedRoute` is defined by `LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS` (54&nbsp;minutes by default) or the value of the `ACCESS_TOKEN_EXPIRE_SECONDS` environment variable.
18+
19+
## State updates when using Clerk
20+
21+
When `CLERK_AUTH_ENABLED` is `true`, token refresh from the frontend is skipped. After signing in with Clerk, `ClerkAuthAdapter` logs the user in to the backend and calls `login()` from the authentication context, which updates several states:
22+
23+
| State location | Value after Clerk token update |
24+
|----------------|--------------------------------|
25+
| `access_token_lf` cookie | Clerk session token |
26+
| `refresh_token_lf` cookie | Backend refresh token |
27+
| `auto_login_lf` cookie | `"login"` |
28+
| Local storage `access_token_lf` | Clerk session token |
29+
| `authStore.accessToken` | Clerk session token |
30+
| `authStore.isAuthenticated` | `true` |
31+
| `authContext.userData` | populated from `/users/whoami` |
32+
33+
With Clerk enabled the periodic refresh is disabled and all further state changes rely on Clerk sessions.
34+
35+
## Environment variables
36+
37+
| Variable | Format | Default | Description |
38+
|----------|--------|---------|-------------|
39+
| <Link id="CLERK_AUTH_ENABLED"/>CLERK_AUTH_ENABLED | Boolean | `false` | When enabled, Langflow uses Clerk for authentication. After signing in or signing up with Clerk, the frontend registers the user with the backend and then logs in via `/api/v1/login` to sync cookies. The `access_token_lf` cookie stores the Clerk session token. |
40+
| <Link id="CLERK_PUBLISHABLE_KEY"/>CLERK_PUBLISHABLE_KEY | String | Not set | Publishable key required when `CLERK_AUTH_ENABLED` is `true`. |

src/backend/base/langflow/services/auth/clerk_utils.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
import httpx
77
from fastapi import HTTPException, Request, status
8+
from fastapi.responses import JSONResponse
89
from jose import JWTError, jwk, jwt
910
from sqlmodel.ext.asyncio.session import AsyncSession
10-
from src.backend.base.langflow.logging.logger import logger
11+
from starlette.status import HTTP_401_UNAUTHORIZED
1112

13+
from langflow.logging.logger import logger
1214
from langflow.services.database.models.user import User, UserCreate
1315
from langflow.services.database.models.user.crud import get_user_by_id
1416
from langflow.services.deps import get_settings_service
@@ -19,7 +21,7 @@
1921
_jwks_cache: dict[str, dict[str, Any]] = {}
2022

2123
# APIs that require Clerk token decoding in middleware
22-
PROTECTED_PATHS = ["/api/v1/users/"]
24+
PROTECTED_PATHS = ["/api/v1/users/","/api/v1/login"]
2325

2426

2527
async def _get_jwks(issuer: str) -> dict[str, Any]:
@@ -146,13 +148,25 @@ async def clerk_token_middleware(request: Request, call_next):
146148
ctx_token: Token | None = None
147149
if settings.auth_settings.CLERK_AUTH_ENABLED and request.url.path in PROTECTED_PATHS:
148150
auth_header = request.headers.get("Authorization")
151+
152+
if not auth_header or not auth_header.startswith("Bearer "):
153+
logger.warning("Missing or malformed Authorization header for Clerk protected route.")
154+
return JSONResponse(
155+
status_code=HTTP_401_UNAUTHORIZED,
156+
content={"detail": "Authorization header with valid Bearer token required"},
157+
)
158+
149159
if auth_header and auth_header.startswith("Bearer "):
150160
token = auth_header[len("Bearer ") :]
151161
try:
152162
payload = await verify_clerk_token(token)
153163
ctx_token = auth_header_ctx.set(payload)
154164
except Exception as exc: # noqa: BLE001
155165
logger.warning(f"Failed to verify Clerk token: {exc}")
166+
return JSONResponse(
167+
status_code=HTTP_401_UNAUTHORIZED,
168+
content={"detail": "Invalid Clerk token"}
169+
)
156170

157171
try:
158172
return await call_next(request)

src/backend/base/langflow/services/settings/auth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class AuthSettings(BaseSettings):
5050
COOKIE_DOMAIN: str | None = None
5151
"""The domain attribute of the cookies. If None, the domain is not set."""
5252

53+
CLERK_AUTH_ENABLED: bool = False
54+
CLERK_PUBLISHABLE_KEY: str | None = None
55+
5356
pwd_context: CryptContext = CryptContext(schemes=["bcrypt"], deprecated="auto")
5457

5558
model_config = SettingsConfigDict(validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_")

src/frontend/package-lock.json

Lines changed: 95 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"dependencies": {
66
"@chakra-ui/number-input": "^2.1.2",
7+
"@clerk/clerk-react": "^5.32.4",
78
"@headlessui/react": "^2.0.4",
89
"@hookform/resolvers": "^3.6.0",
910
"@million/lint": "^1.0.0-rc.26",

0 commit comments

Comments
 (0)