langchain-ai / langserve

LangServe 🦜️🏓
Other
1.85k stars 202 forks source link

Security Auth API Key headers compatibility with playground #272

Open jokerslab opened 9 months ago

jokerslab commented 9 months ago

When adding middleware for auth api-key header security on the langserve routes, the /docs page doesn't recognize the security so there is no option to add the header and the /playground pages don't have an option of adding the header so it doesn't work.

Is there any additional documentation on how to setup auth security on these routes and still be able to use the playground?

eyurtsev commented 9 months ago

For /docs see this solution: https://github.com/tiangolo/fastapi/issues/364

ccing @dqbd for playground

Wildhammer commented 8 months ago

Can someone explain how this can be done? I have added authorization to all the endpoints except for playground endpoint but the problem is the API calls in the playground frontend app need to send the authorization header. How can that be achieved?

pjbhaumik commented 8 months ago

+1

anbhimi commented 8 months ago

Can someone explain how this can be done? I have added authorization to all the endpoints except for playground endpoint but the problem is the API calls in the playground frontend app need to send the authorization header. How can that be achieved?

This would be of much help. Thanks for posting the question.

Wildhammer commented 8 months ago

@dqbd any input on this?

Wildhammer commented 7 months ago

@dqbd appreciate if we can get some guidance on this.

nat-n commented 6 months ago

Same issue here, it'd like to use the playground with a service where the langserve routes require an Authentication header, so I'm looking for a way to configure the playground to get a header from the page or somewhere and include it in requests.

dcaputo-harmoni commented 4 months ago

This would be a very useful feature; any production environment likely needs some form of authentication header. We are having to resort to curl statements for testing.

mattbajorek commented 3 months ago

As a temporary workaround you can use secure http cookie authentication with some templated html around all routes similar to what is in the following server code with an environment variable set for the API Key (LANGSERVE_API_KEY). This will give you a sort of dashboard for navigation after login:

import os

from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse

from app.example_chain import example_chain
from langserve import add_routes

LANGSERVE_API_KEY = os.getenv("LANGSERVE_API_KEY")

if not LANGSERVE_API_KEY:
    raise ValueError("LANGSERVE_API_KEY is not set")

def get_login_html_content(hasError: bool = False):
    error_message = "<p>Invalid API Key</p>" if hasError else ""
    return f"""
    <html>
        <head>
            <title>Login</title>
        </head>
        <body>
            <form action="/login" method="post">
                <input type="text" name="api_key" placeholder="Enter API Key" required>
                <button type="submit">Submit</button>
            </form>
            {error_message}
        </body>
    </html>
    """

app = FastAPI(
    title="Example LangChain Server",
    version="1.0",
    description="Example AI Microservice",
)

@app.middleware("http")
async def cookie_auth_middleware(request: Request, call_next):
    path = request.url.path
    # Check if the path is one of the unprotected routes
    if path not in ["/", "/login"]:
        # Extract the cookie and check it
        api_key = request.cookies.get("LANGSERVE_API_KEY")
        if api_key != LANGSERVE_API_KEY:
            return RedirectResponse(url="/login", status_code=303)
    response = await call_next(request)
    return response

@app.get("/", response_class=RedirectResponse)
async def redirect_root_to_login():
    return RedirectResponse("/login")

@app.get("/login", response_class=HTMLResponse)
def login_form():
    return HTMLResponse(content=get_login_html_content())

@app.post("/login")
def login(api_key: str = Form(...)):
    if api_key == LANGSERVE_API_KEY:
        response = RedirectResponse(url="/dashboard", status_code=303)
        response.set_cookie(
            key="LANGSERVE_API_KEY",
            value=api_key,
            httponly=True,
            secure=True,
            samesite="strict",
        )
        return response
    return HTMLResponse(content=get_login_html_content(hasError=True))

routes = [
    {
        "path": "/example_path",
        "chain": example_chain,
        "name": "Example",
    }
]

routes_html = "\n".join(
    [f'<a href="{route["path"]}/playground">{route["name"]}</a>' for route in routes]
)

@app.get("/dashboard", response_class=HTMLResponse)
def login_form():
    return HTMLResponse(
        content=f"""
    <html>
        <head>
            <title>Dashboard</title>
            <style>
                a {{
                    display: block;
                }}
            </style>
        </head>
        <body>
            <h3>Dashboard</h3>
            <a href="/docs">Docs</a>
            <h3>Playgrounds</h3>
            {routes_html}
        </body>
    </html>
    """
    )

for route in routes:
    add_routes(
        app,
        route["chain"],
        path=route["path"],
    )

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)