langserve icon indicating copy to clipboard operation
langserve copied to clipboard

Security Auth API Key headers compatibility with playground

Open jokerslab opened this issue 1 year ago • 9 comments

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?

jokerslab avatar Nov 30 '23 15:11 jokerslab

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

ccing @dqbd for playground

eyurtsev avatar Nov 30 '23 17:11 eyurtsev

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?

Wildhammer avatar Dec 12 '23 19:12 Wildhammer

+1

pjbhaumik avatar Dec 19 '23 20:12 pjbhaumik

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.

anbhimi avatar Dec 19 '23 20:12 anbhimi

@dqbd any input on this?

Wildhammer avatar Dec 27 '23 21:12 Wildhammer

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

Wildhammer avatar Jan 24 '24 13:01 Wildhammer

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.

nat-n avatar Feb 19 '24 18:02 nat-n

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.

dcaputo-harmoni avatar Apr 08 '24 03:04 dcaputo-harmoni

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)

mattbajorek avatar May 23 '24 13:05 mattbajorek