langchain-ai / langchain-ai/langgraph

Custom auth HTTPException status_code is always converted to 403

Open
#6,552 1 comment 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

external
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):

  1. In langgraph_api/auth/custom.py, the CustomAuthBackend.authenticate() method catches Auth.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
  1. Starlette's AuthenticationError doesn't have a status_code attribute - it only carries the message.

  2. In langgraph_api/auth/middleware.py, the on_error handler 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.