R
Railway•9mo ago
nersaira

How to secure frontend with authorizer

If I deploy the langflow and authorizer templates to two instances, how do I set it up so that I need to log into authorizer first to be able to see the langflow interface? How to set it up in general for any github package with a webserver so that it is protected through some interace or authorizer? Can I change the URL? Is there a secure forwarding? Project ID: c7df755c-dc65-45a0-84a6-2c9a80edc415 ProjectID: 855a6c19-b8d9-4df8-a5d2-e8ef9f7b1637
Solution:
Project ID: 2623fad8-b2a5-4b41-89c5-cb8771b12356
Jump to solution
65 Replies
Solution
Percy
Percy•9mo ago
Project ID: 2623fad8-b2a5-4b41-89c5-cb8771b12356
Brody
Brody•9mo ago
if you've solved this thread we would all appreciate it if you could tell us how it was solved, for anyone in the future who may have this question too
nersaira
nersaira•9mo ago
No I don't know how to solve this myself ^^
Brody
Brody•9mo ago
you marked this as solved, was that a miss-click?
nersaira
nersaira•9mo ago
yeah, can I delete the thread and reopen it?
Brody
Brody•9mo ago
fixed but I'll admit I don't know how to solve this issue for you, but I'll look into it and see what I can find, no promises though
nersaira
nersaira•9mo ago
I have looked at all resources. I deployed it from here: https://docs.authorizer.dev/deployment/railway The thing is how can you even disable external access of a webserver running?
Authorizer Docs
Your Data Your Control. An Open Source Authentication and Authorization solution for your business. Easy to integrate and quick to implement with available SDKs
Brody
Brody•9mo ago
I I'll look into it, but again no promises
nersaira
nersaira•9mo ago
okay thanks, wouldbe great if you can find it out
Brody
Brody•9mo ago
okay so unfortunately you have the wrong idea about what authorizer is, its not something that you can just stick infront of anything web based and add a login screen to, it would have to be implemented in code, and unfortunately langflow does not have an intigration
nersaira
nersaira•9mo ago
okay I see thanks Brody Is there any other solution if you want to secure your railway instance with password protection?
Brody
Brody•9mo ago
a proxy with basicauth? but does langflow not have a login page itself?
nersaira
nersaira•9mo ago
no or at least if i use the template from their docs it does not - maybe from the docker, but how to set that up on railway how do i use a proxy with basicauth
Brody
Brody•9mo ago
looks like you could probably just modify the langflow repo that railway cloned into your account and add an authentication middleware
nersaira
nersaira•9mo ago
okay thanks, can you maybe point me at a tutorial or the name of a compatible authentication middleware?
Brody
Brody•9mo ago
I'm sorry I don't have any on hand, I've never used langflow myself
nersaira
nersaira•9mo ago
okay i see, thank you
Floris
Floris•9mo ago
hey bro send me the repo ill help u theres no one-size-fits all tutorial thats the nice thing, you can pretty much use any oauth2 provider as login account if u show me what ur repo runs on (apache servers or ngix whatever) i can help u @nersaira (0.5.0a1) is being developed with fleshing out authentication to production environments it's in a dev branch right now which is downloadable semi- unstable however up until then you could just go to the index.tsx file where ur main components are being rendered into reactDOM (i usually use Nextjs for auth), but for react u have the React Router so you will wrap this page with your routed wrapper (LangFlowRouter) whatever u wanna call it and you replace the main route of your App with the LangFlowRouter which u just made logging out u dont have to bother if ur the only user, just remove the token from ur browser also CORS is really easy to setup so your backend will ONLY take requests from your frontend domain i can help you tomorrow as i need to go soon, but simply said, 1 make a firestore account and setup their oauth2, 2 setup an authentication endpoint with firestore (generate a json key in the firestore console), this is how u authenticate to ur firestore 3 either return a token like jwt or something different and cache/save the passwords with like salt hashers if u dont wanna use firestore, or bcrypt 4 download a simple tailwind css login page component, setup an ajax request with your form data to your firestore or backend (whatever u like) 5 create the react router like i mentioned earlier and wrap the langflow index page with the most important components in here (cors should take care of a lot arleady since react loads different than normal html etc so it will block out alot alrdy), example (i hate js so mind my code but this should give you an idea)
// import react router and the redirect to serve your wrapper langflow index
import { Route, Redirect } from "react-router-dom";

// super basic login vars
function ProtectedRoute({ component: Component, ...rest }) {
const token = localStorage.getItem('token');
const isAuthenticated = token !== null;

// this is where you will wrap your components in from langflow
return (
<Route
{...rest}
render={props =>
// when the user is authed you will show the components (langflow)
isAuthenticated ? (
<Component {...props} />
) : (
// route your login page from here
<Redirect to="/login" />
)
}
/>
);
}
// import react router and the redirect to serve your wrapper langflow index
import { Route, Redirect } from "react-router-dom";

// super basic login vars
function ProtectedRoute({ component: Component, ...rest }) {
const token = localStorage.getItem('token');
const isAuthenticated = token !== null;

// this is where you will wrap your components in from langflow
return (
<Route
{...rest}
render={props =>
// when the user is authed you will show the components (langflow)
isAuthenticated ? (
<Component {...props} />
) : (
// route your login page from here
<Redirect to="/login" />
)
}
/>
);
}
index.tsx from langflow 0.5.0A1
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";

import { ApiInterceptor } from "./controllers/API/api";
// @ts-ignore
import "./style/index.css";
// @ts-ignore
import "./style/applies.css";
// @ts-ignore
import "./style/classes.css";

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<ContextWrapper>
<BrowserRouter>
<App />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);
reportWebVitals();
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";

import { ApiInterceptor } from "./controllers/API/api";
// @ts-ignore
import "./style/index.css";
// @ts-ignore
import "./style/applies.css";
// @ts-ignore
import "./style/classes.css";

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<ContextWrapper>
<BrowserRouter>
<App />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);
reportWebVitals();
if you wrap it like i showed you above it would look something like this
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route } from "react-router-dom";
import App from "./App";
// get a random TailWind CSS login page, any will do really it doesnt matter how it looks
import LoginPage from "./components/LoginPage";
import ProtectedRoute from "./components/ProtectedRoute";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";

import { ApiInterceptor } from "./controllers/API/api";
import "./style/index.css";
import "./style/applies.css";
import "./style/classes.css";

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);

// langflow components
root.render(
<ContextWrapper>
<BrowserRouter>
<Route path="/login" component={LoginPage} />
<ProtectedRoute path="/" component={App} />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);

reportWebVitals();
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route } from "react-router-dom";
import App from "./App";
// get a random TailWind CSS login page, any will do really it doesnt matter how it looks
import LoginPage from "./components/LoginPage";
import ProtectedRoute from "./components/ProtectedRoute";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";

import { ApiInterceptor } from "./controllers/API/api";
import "./style/index.css";
import "./style/applies.css";
import "./style/classes.css";

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);

// langflow components
root.render(
<ContextWrapper>
<BrowserRouter>
<Route path="/login" component={LoginPage} />
<ProtectedRoute path="/" component={App} />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);

reportWebVitals();
for the login page request, just declare 2 empty constants for the username and password wrapped in a function, and then send a post request using axios or whatever to your backend or firestore, the response will be your token that you will cache so you can stay authed
import React, { useState } from 'react';
import axios from 'axios';

function LangFlowLoginPage() {
// define your empty strings
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
import React, { useState } from 'react';
import axios from 'axios';

function LangFlowLoginPage() {
// define your empty strings
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
im trying to do it in very easy to understand blocks so you can follow it if you need more help i can help u tomorrow anyways so you have defined your credentials, now you just need to handle the submission and make your post request using axios
const handlesubmit = async (e) => {
e.preventDefault();
// we use try in these scenarios to gracefully handle the responses (server could be offline etc or wrong credentials etc)
try {
// we define where we get our response from and where we save it in
const response = await axios.post('https://YOUR_AUTHENTICATION_DOMAIN.COM/AUTH', {
username: username,
password: password
});
const handlesubmit = async (e) => {
e.preventDefault();
// we use try in these scenarios to gracefully handle the responses (server could be offline etc or wrong credentials etc)
try {
// we define where we get our response from and where we save it in
const response = await axios.post('https://YOUR_AUTHENTICATION_DOMAIN.COM/AUTH', {
username: username,
password: password
});
we now have the server telling us whether we have access yes or no so now we handle the response accordingly
Brody
Brody•9mo ago
quick question morpheus, are these ai code snippets?
Floris
Floris•9mo ago
// so we are now calling the attributes of response and data, being the acces token
localStorage.setitem('token', response.data.accessToken);
window.location.href = '/';
// from here you send yourself FROM the login page TO the domain of your langflow
// so we are now calling the attributes of response and data, being the acces token
localStorage.setitem('token', response.data.accessToken);
window.location.href = '/';
// from here you send yourself FROM the login page TO the domain of your langflow
Floris
Floris•9mo ago
No description
Floris
Floris•9mo ago
no haha my js isnt amazing so big chance there will be some mistakes so now hooking back to our route
// langflow components
root.render(
<ContextWrapper>
<BrowserRouter>
<Route path="/login" component={LoginPage} />
<ProtectedRoute path="/" component={App} />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);

reportWebVitals();
// langflow components
root.render(
<ContextWrapper>
<BrowserRouter>
<Route path="/login" component={LoginPage} />
<ProtectedRoute path="/" component={App} />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);

reportWebVitals();
(i have no clue what reportWebVitals still holds as this comes straight from the langflow repo)
Floris
Floris•9mo ago
No description
Floris
Floris•9mo ago
not that important if you are the only user, if ur on vercel and stuff with NextJS u probably seen the TimeTillFirstByte thats what this tracks no clue what the other ones do so u can probably leave as is for CORS you can get an easy chrome extension and lastly
src\frontend\src\pages
src\frontend\src\pages
here are are the remaining pages, im not sure if they are accessable from the display domain
import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage() {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } =
useContext(TabsContext);

// Set a null id
useEffect(() => {
setTabId("");
}, []);
const navigate = useNavigate();

// Personal flows display
return (
<>
<Header />
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
</>
);
}
import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage() {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } =
useContext(TabsContext);

// Set a null id
useEffect(() => {
setTabId("");
}, []);
const navigate = useNavigate();

// Personal flows display
return (
<>
<Header />
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
</>
);
}
this is the most important one from langflow the community page i'd ignore if i was you it's no personal data bound to u on there rest looks like js junk so thats probably fine bro i hope i explained it ok
Brody
Brody•9mo ago
im gonna be real with you, that was far too complex
Floris
Floris•9mo ago
huh nah bro i promise it looks way complexer than it is read thru it for 10 min follow all the comments i wrote and u will understand i coded alot with NextJs and thats pretty much only components they got rid of the React Router but got the app router which is way better
Brody
Brody•9mo ago
alright we will wait to hear back from op
Floris
Floris•9mo ago
but this is still old react i dont know how much u did with frontend brody but react is basicly just the reject older brother of nextjs it used to be good but nextjs is SPEEEEED and way securer with nextauth
Brody
Brody•9mo ago
they wanted to secure langflow
Floris
Floris•9mo ago
they did in this dev branch or like partly if this is too much you can look into CloudFlare i think they have these Zero Secority pages something like that i havent really toyed around with themn
Brody
Brody•9mo ago
that doesnt secure langflow from the railway side of things though
Floris
Floris•9mo ago
i mean is the railway repo open for pull requests? the fork we could add this pretty easily but the thing is, its an open source repo its so hard to 100% protect if you really want to get in u will it has 5000 commits its so hard to protect that the SAFEST is to just run it on a VM somewhere and just RDP into that VM or at home but thats alot of gpu power
Brody
Brody•9mo ago
they just wanted to put langflow behind a login page
Floris
Floris•9mo ago
yea i mean for the railway devteam it would be pretty easy since they serve the data and domain
Brody
Brody•9mo ago
thats not something railway needs to do though the user can do that
Floris
Floris•9mo ago
that is true bro what is langflow anyways compared to langchain whats new
Brody
Brody•9mo ago
no clue
nersaira
nersaira•9mo ago
Hey you two, wow I don't know what to say. 🤣 🤣 🤣 what happened here? first of all thank you, but this way too complicated 😄 - since you did all the work, would you mind just submitting it to the langflow repo, so that they can implement it?
Floris
Floris•9mo ago
im not a contributor to the main langflow repo its not alot of work it just looks complex tldr: secure a few components with a wrapped authenticated route with firestore
nersaira
nersaira•9mo ago
also i found out that it is maybe possible to enable user authetification in langflow by setting the enivornment variables for password and username but the railway template doesn't allow to add variables before booting up
Brody
Brody•9mo ago
add them after
nersaira
nersaira•9mo ago
would you maybe know how to deploy langflow with those two variables installed i tried, but it doenst seem to do much
Brody
Brody•9mo ago
lets see what you tried
nersaira
nersaira•9mo ago
thanks a lot mate, i am gonna keep that in mind for a last resort 😅
Floris
Floris•9mo ago
you just deploy the unborn head first of ur repo, let it setup and then set the env variables and it should automatically reboot with them injected into ur deploy
Brody
Brody•9mo ago
unborn head?
Floris
Floris•9mo ago
first deploy or commit is an unborn head
Brody
Brody•9mo ago
dont call it that lmao
Floris
Floris•9mo ago
github calls it that
Brody
Brody•9mo ago
yuck
nersaira
nersaira•9mo ago
also where to mount the additional repo with this volume, so that langflow stays persistent?
Floris
Floris•9mo ago
i havent worked with that repo is it not just caching the data locally in the deploy to a drive or ?
nersaira
nersaira•9mo ago
not sure how to load is a docker file? that should work shouldn't it?
Floris
Floris•9mo ago
yes railway will handle dockerfiles just fine if thats what you mean but you shouldnt have to deal with that really
nersaira
nersaira•9mo ago
but i think if i load a docker file with a volume directly and the correct env files it could work or i can use cli to check env variables
Floris
Floris•9mo ago
u dont need env files just define them in the 'Variables' tab of your deploy however since you say it's been coded in already, langflow would be looking for 2 specific variable names so you have to figure out what those are maybe just PASSWORD and USERNAME
nersaira
nersaira•9mo ago
i have them and i put them in the normal railway setup, but it doesn't work
Floris
Floris•9mo ago
can you show me a screenshot of the variables tab?
nersaira
nersaira•9mo ago
wait i am booting up a docker file in the same instance
Floris
Floris•9mo ago
alright bro
nersaira
nersaira•9mo ago
interesting here i can also delete and add the domain name manually now that way i could also use something like authorizer.dev or not?
Floris
Floris•9mo ago
i mean technically your public domain will always be exposed, so if someone checked all railway url's programatically they would inevitably find your deployed langflow UI so that's not really a safe option
nersaira
nersaira•9mo ago
i see
Floris
Floris•9mo ago
do you know by chance if the langflow UI is open source? it's beautiful and i have need for something similair with these little connected nodes
nersaira
nersaira•9mo ago
can't say