Skip to content

Commit

Permalink
Don't CSRF protect Authorization: Bearer ..., refs #11
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jul 1, 2020
1 parent 01a428d commit 0d23766
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ If it cannot find that environment variable, it will generate a random secret wh

This means that if you do not configure a specific secret your user's `csrftoken` cookies will become invalid every time the server restarts! You should configure a secret.

## Other cases that skip CSRF protection

* If the request includes an `Authorization: Bearer ...` header, commonly used by OAuth and JWT authentication, the request will not be required to include a CSRF token. This is because browsers cannot send those headers in a context that can be abused.

## Limitations

* Currently only works for `application/x-www-form-urlencoded` forms, not `multipart/form-data` forms (with file uploads)
8 changes: 8 additions & 0 deletions asgi_csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ async def wrapped_send(event):
# x-csrftoken header matches
await app(scope, receive, wrapped_send)
return
# Authorization: Bearer skips CSRF check
if (
headers.get(b"authorization", b"")
.decode("latin-1")
.startswith("Bearer ")
):
await app(scope, receive, wrapped_send)
return
# We need to look for it in the POST body
content_type = headers.get(b"content-type", b"").split(b";", 1)[0]
if content_type == b"application/x-www-form-urlencoded":
Expand Down
14 changes: 14 additions & 0 deletions test_asgi_csrf.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,17 @@ async def test_multipart_not_supported(csrftoken):
files={"csv": ("data.csv", "blah,foo\n1,2", "text/csv")},
cookies={"csrftoken": csrftoken},
)


@pytest.mark.asyncio
@pytest.mark.parametrize(
"authorization,expected_status", [("Bearer xxx", 200), ("Basic xxx", 403)]
)
async def test_post_with_authorization(authorization, expected_status):
async with httpx.AsyncClient(
app=asgi_csrf(hello_world_app, signing_secret=SECRET)
) as client:
response = await client.post(
"http://localhost/", headers={"Authorization": authorization}
)
assert expected_status == response.status_code

0 comments on commit 0d23766

Please sign in to comment.