Authentication

GraphQL servers vary in how they expect authentication. This page covers the patterns supported by aiographql.client.GraphQLClient and the layer each one belongs to.

Headers are layered in the following order of precedence (least to greatest):

  1. Client-default headers passed to GraphQLClient at construction.

  2. Per-request headers on GraphQLRequest.

  3. Headers passed to query() itself.

A later layer overrides the same header from an earlier one.

Bearer Tokens

The common case. Set Authorization once on the client, reuse it for every request:

client = GraphQLClient(
    endpoint="https://api.github.com/graphql",
    headers={"Authorization": f"Bearer {token}"},
)
response = await client.query("{ viewer { login } }")

Basic Authentication

Standard HTTP basic auth fits the same shape. Encode user:password as Base64 and set Authorization:

import base64

credentials = base64.b64encode(f"{user}:{password}".encode()).decode()
client = GraphQLClient(
    endpoint="https://api.example.com/graphql",
    headers={"Authorization": f"Basic {credentials}"},
)

Per-request Override

To call a service with multiple identities (for example, an admin client that sometimes acts on behalf of a user), override the header per request without constructing a new client:

response = await client.query(
    request="{ me { id } }",
    headers={"Authorization": f"Bearer {user_token}"},
)

The same pattern is the right place for one-off headers such as X-Request-Id or feature flags.

Refreshing Short-lived Tokens

Bearer tokens that expire need to be refreshed before each request. The client does not provide a refresh hook; instead, hold the refresh logic in a helper that returns a fresh token, and pass it via the per-request headers argument:

async def authorized_query(client, request, token_source):
    token = await token_source.get_valid_token()
    return await client.query(
        request=request,
        headers={"Authorization": f"Bearer {token}"},
    )

For long-lived clients that own their token state, cache the token on the helper and only refresh when expiry is close:

import time

class TokenSource:
    def __init__(self, fetch):
        self._fetch = fetch
        self._token: str | None = None
        self._expires_at: float = 0.0

    async def get_valid_token(self) -> str:
        if self._token is None or time.monotonic() > self._expires_at - 30:
            token, ttl = await self._fetch()
            self._token = token
            self._expires_at = time.monotonic() + ttl
        return self._token

Cookies and Session-based Auth

Servers that authenticate via cookies (Django, Rails, set-cookie flows) need a transport session that persists cookies across requests. Pass a managed aiohttp.ClientSession or httpx.AsyncClient and the cookie jar is reused for every call:

import aiohttp
from aiographql.client import GraphQLClient

async with aiohttp.ClientSession() as session:
    # Log in once. The session keeps the cookies.
    await session.post(
        "https://api.example.com/login",
        json={"user": user, "password": password},
    )

    client = GraphQLClient(
        endpoint="https://api.example.com/graphql",
        session=session,
    )
    response = await client.query("{ me { id } }")

The same approach works with httpx.AsyncClient if the project uses the httpx extra.

API Keys in Custom Headers

Some servers expect the key in a non-Authorization header. There is no special API for this; treat it like any other default header:

client = GraphQLClient(
    endpoint="https://api.example.com/graphql",
    headers={"X-API-Key": api_key},
)

Subscriptions

WebSocket subscriptions follow the graphql-ws protocol, which sends the auth payload during the connection_init step. The headers configured on the client and request are forwarded into that payload by the subscription transport, so the same Bearer / API key patterns above apply.

For a server that requires a custom connectionParams shape, set the matching keys via request headers; the subscription transport passes them through unchanged.