langchain-ai / langchain-ai/langgraph
Custom auth HTTPException status_code is always converted to 403
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 41.9k
- Forks
- 7.1k
- Avg merge
- 23h 7m
- Merged PRs (30d)
- 30
Description
Checked other resources
- This is a bug, not a usage question.
- I added a clear and detailed title that summarizes the issue.
- I read what a minimal reproducible example is.
- I included a self-contained, minimal example that demonstrates the issue.
Example Code
from langgraph_sdk import Auth
auth = Auth()
@auth.authenticate
async def get_current_user(authorization: str | None):
if not authorization:
# This should return HTTP 401, but returns 403
raise auth.exceptions.HTTPException(
status_code=401, detail="No token provided."
)
# ... validate token ...
if token_invalid:
# This should also return HTTP 401, but returns 403
raise auth.exceptions.HTTPException(
status_code=401, detail="Invalid token"
)
return {"identity": "user-id"}
Error Message and Stack Trace (if applicable)
Expected HTTP response: 401 Unauthorized
Actual HTTP response: 403 Forbidden
Description
When using custom authentication with @auth.authenticate, any Auth.exceptions.HTTPException raised with status_code=401 is converted to a 403 Forbidden response.
Root Cause Analysis (from langgraph-api==0.5.3):
- In
langgraph_api/auth/custom.py, theCustomAuthBackend.authenticate()method catchesAuth.exceptions.HTTPException:
except Auth.exceptions.HTTPException as e:
if e.status_code == 401 or e.status_code == 403:
raise AuthenticationError(e.detail) from None # <-- status_code is LOST here
-
Starlette's
AuthenticationErrordoesn't have astatus_codeattribute - it only carries the message. -
In
langgraph_api/auth/middleware.py, theon_errorhandler always returns 403:
def on_error(conn: HTTPConnection, exc: AuthenticationError):
return JSONResponse({"detail": str(exc)}, status_code=403) # <-- Always 403!
Impact:
- Security implications: 401 (Unauthorized) and 403 (Forbidden) have different semantic meanings
- 401 indicates missing/invalid credentials and typically prompts re-authentication
- 403 indicates the user is authenticated but lacks permission
- This breaks standard HTTP semantics and client-side error handling logic
Suggested Fix:
Option 1: Preserve status code through the error chain
# In custom.py
class AuthenticationErrorWithStatus(AuthenticationError):
def __init__(self, message: str, status_code: int = 401):
super().__init__(message)
self.status_code = status_code
# When catching HTTPException
raise AuthenticationErrorWithStatus(e.detail, e.status_code) from None
# In middleware.py
def on_error(conn: HTTPConnection, exc: AuthenticationError):
status = getattr(exc, 'status_code', 403)
return JSONResponse({"detail": str(exc)}, status_code=status)
Option 2: Re-raise HTTPException directly instead of converting to AuthenticationError
System Info
- langgraph-api version: 0.5.3 (also likely affects newer versions up to 0.5.32)
- langgraph-sdk version: Any version with
Auth.exceptions.HTTPException - Python version: 3.12
- OS: macOS / Linux
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in langgraph_api/auth/custom.py and langgraph_api/auth/middleware.py, following the custom authentication exception path from the provided example. Reproduce the 401 case, then add or update coverage for the expected response status and verify that both 401 and 403 behavior remain correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication, backend, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100