clerk / clerk/clerk-sdk-python

Authorization header parsing is case sensitive and consumes non-Bearer schemes, so a valid __session cookie is ignored

Open Beginner friendly
#243 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
186
Forks
47
Avg merge
18h 43m
Merged PRs (30d)
1

Description

Summary

_get_session_token in clerk_backend_api/security/authenticaterequest.py pulls the token
out of the header with

bearer_token = request.headers.get('Authorization')
if bearer_token is not None:
    return bearer_token.replace('Bearer ', '')

That is a substring delete and it is case sensitive, so two ordinary requests fail to
authenticate when they should not.

Reproduction

clerk-backend-api 6.0.1. I generate an RSA key locally, sign a session-shaped JWT with it and
pass the public PEM as jwt_key, so verification is real and needs no Clerk instance.

opts = AuthenticateRequestOptions(jwt_key=PUBLIC_PEM)
authenticate_request(Req({"Authorization": "bearer " + token}), opts)
Authorization: Bearer <token>              -> signed-in
Authorization: bearer <token>              -> signed-out  token-invalid
Authorization: BEARER <token>              -> signed-out  token-invalid
Authorization: Bearer  <token> (2 spaces)  -> signed-out  token-invalid-signature
cookie only, __session=<token>             -> signed-in
Authorization: Basic ... plus a valid __session cookie -> signed-out  token-invalid

Same run, as a control, so this is not a harness that fails everything: a matching
authorized_parties signs in and returns sub, a non-matching one gives
token-invalid-authorized-parties, an expired token gives token-expired, and a token signed
by a different key gives token-invalid-signature.

Two more details from the same line. 'Bearer aBearer b' extracts 'ab', and with a Basic
header present the extractor returns the literal string Basic dXNlcjpwYXNzd29yZA==, which
get_token_type then classifies as a SESSION_TOKEN.

authenticate_request_async shares the extractor, so both paths behave the same.

Why I think it is worth fixing

RFC 7235 section 2.1 makes the auth-scheme case insensitive, so bearer is a legal header that
this SDK rejects. The second case is the one I would worry about more. An app behind basic auth
(staging environments, some proxies) has its Authorization header set by something other than
Clerk, and the valid __session cookie is then never looked at, because the header is consumed
whatever it contains.

A prefix check would cover both:

value = request.headers.get('Authorization')
if value is not None:
    scheme, _, rest = value.partition(' ')
    if scheme.lower() == 'bearer' and rest.strip():
        return rest.strip()
    # fall through to the cookie instead of returning a non-token

Happy to send that as a PR with tests if you would rather review a diff.

How I found it, and what I am not claiming

I maintain a test harness that measures whether coding models can drive a given SDK, by running
the code they write and asserting on the HTTP that comes out. This finding did not come from
that. I read the token extraction path while looking at how the SDK handles credentials, and
these cases came from constructing them.

No model produced this, so it is latent rather than measured, and I would rather say so.

One limit on the repro: I sign the token myself and verify it networklessly with jwt_key, so
what I can show is which headers your extractor accepts. Whether your hosted instances ever see
a lowercase scheme in practice is your data, not mine.

toolshed is a small studio run by its owner, who directs the work, and AI does a lot of the
engineering.

Cal / toolshed / toolshedlabs@gmail.com

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 clerk_backend_api/security/authenticaterequest.py at _get_session_token and trace how authenticate_request and authenticate_request_async use its result. Add focused coverage for case-insensitive Bearer schemes, extra spaces, and non-Bearer Authorization headers falling back to __session; done means both paths authenticate the valid token in these cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
authentication
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.