# Bearer tokens

`HTTPBearerAuth` reads `Authorization: Bearer <token>`. It is the source for API clients, scripts and service-to-service calls. For JWTs from an identity provider, see [JWT and OAuth 2.0](<https://fastapi-multiauth.d3vyce.fr/guide/jwt-and-oauth2/index.md>).

## Issuing and storing tokens

```python
import os

from fastapi import FastAPI, Security

from fastapi_multiauth import (
    APIKeyCookieAuth,
    HTTPBearerAuth,
    UnauthorizedError,
    hash_token,
)

SECRET_KEY = os.environ["SECRET_KEY"]
TOKEN_HASHES: dict[str, str] = {}


async def validate_session(username: str) -> str:
    return username


async def validate_token(token: str) -> str:
    owner = TOKEN_HASHES.get(hash_token(token))
    if owner is None:
        raise UnauthorizedError()
    return owner


session = APIKeyCookieAuth(
    name="__Host-session", validator=validate_session, secret_key=SECRET_KEY
)
user_tokens = HTTPBearerAuth(validator=validate_token, prefix="user_")

app = FastAPI()


@app.post("/tokens")
async def create_token(owner: str = Security(session)):
    token = user_tokens.generate_token()  # "user_Xk3..."
    TOKEN_HASHES[hash_token(token)] = owner  # store the hash only
    return {"token": token}  # the only time anyone sees it


@app.get("/me")
async def me(owner: str = Security(user_tokens)):
    return {"owner": owner}
```

- `generate_token()` returns 256 random bits with the source's prefix in front.
- Store `hash_token(token)`, never the token. SHA-256 is right for random tokens; it is **not** right for passwords.
- Revoking a token is deleting its row, and it takes effect immediately.
- Tokens are created from a browser session, never from another token, so a leaked token cannot mint more. The [personal access tokens](<https://fastapi-multiauth.d3vyce.fr/recipes/personal-access-tokens/index.md>) recipe also asks for a recent login.

## Prefixes

`prefix="user_"` makes the source only accept tokens starting with `user_`. The check is a string comparison made before your validator runs, so a token with the wrong prefix is refused without hashing it or querying the database. Under `MultiAuth`, it counts as no credential for that source and the next source is tried.

Give each kind of token its own prefix and source, and combine them with [`MultiAuth`](<https://fastapi-multiauth.d3vyce.fr/guide/multiauth/index.md>): each validator only ever sees its own tokens. Prefixes also help secret scanners spot leaked tokens.

## Token generators

`generate_token()` builds the token body with `token_generator`, then puts the prefix in front. The default is `secrets.token_urlsafe`; pass your own callable to change the format. It receives the number of random bytes and returns the body:

```python
HTTPBearerAuth(validator=validate_token, prefix="user_", token_generator=secrets.token_hex)
```

Build it on `secrets`, never `random`. The body must be non-empty and have no leading or trailing whitespace, or `generate_token()` raises `ValueError`.

## Checksummed tokens

`checksum_token_generator` is a ready-made generator that adds a short checksum to each token, GitHub-style. The validator can then refuse a typo without a database lookup:

```python
from fastapi import FastAPI, Security

from fastapi_multiauth import (
    HTTPBearerAuth,
    UnauthorizedError,
    checksum_token_generator,
    hash_token,
    verify_token_checksum,
)

TOKEN_HASHES: dict[str, str] = {}


async def validate_token(token: str) -> str:
    if not verify_token_checksum(token, prefix="user_"):
        raise UnauthorizedError()  # a typo: no lookup needed
    owner = TOKEN_HASHES.get(hash_token(token))
    if owner is None:
        raise UnauthorizedError()
    return owner


user_tokens = HTTPBearerAuth(
    validator=validate_token,
    prefix="user_",
    token_generator=checksum_token_generator,
)

app = FastAPI()


@app.get("/me")
async def me(owner: str = Security(user_tokens)):
    return {"owner": owner}
```

> [!WARNING]
>
> Anyone can compute the checksum. It filters typos; the hash lookup is still what authenticates.
