Metadata-Version: 2.4
Name: dead-simple-oauth-fastapi
Version: 0.1.0
Summary: Dead simple Google + GitHub OAuth for FastAPI with built-in security (state + PKCE) and Redis support
Keywords: fastapi,oauth,google,github,authentication
Author: Kamronbek Atajanov
Author-email: Kamronbek Atajanov <atajanovkamronbek2003@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Dist: fastapi>=0.135.3
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.12.5
Requires-Dist: redis[hiredis]>=7.4.0 ; extra == 'redis'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/KAMRON-DEVELOPER/dead-simple-oauth-fastapi
Project-URL: Repository, https://github.com/KAMRON-DEVELOPER/dead-simple-oauth-fastapi
Project-URL: Issues, https://github.com/KAMRON-DEVELOPER/dead-simple-oauth-fastapi/issues
Provides-Extra: redis
Description-Content-Type: text/markdown

# dead-simple-oauth-fastapi

This package helps you add `Google` and `GitHub` `OAuth2` `web application flow` to your FastAPI app.  
No more copying the oauth code/logic in every new project.

What it does for you:

- Redirects the user to Google or GitHub
- Receives the callback
- Validates `state` and `PKCE` automatically
- Returns a typed user object

## Installation

```bash
# Basic (single-instance deployment)
uv add dead-simple-oauth-fastapi

# With Redis (multi-instance deployment)
uv add "dead-simple-oauth-fastapi[redis]"
```

## Google example

```python
from typing import Annotated

from fastapi import FastAPI, Depends, Request
from fastapi.responses import RedirectResponse

from dead_simple_oauth_fastapi import GoogleOAuthClient, MemoryStore, GoogleUser


google = GoogleOAuthClient(
    client_id="YOUR_GOOGLE_CLIENT_ID",
    client_secret="YOUR_GOOGLE_CLIENT_SECRET",
    redirect_uri="http://localhost:8000/auth/google/callback",
    store=MemoryStore(), # Use RedisStore(redis_instance) in multi-instance deployment
)


app = FastAPI()


@app.get("/auth/google")
async def google_oauth_handler(request: Request) -> RedirectResponse:
    return await google.redirect(request)


@app.get("/auth/google/callback")
async def google_oauth_callback_handler(
    oauth_user: Annotated[GoogleUser, Depends(google.callback_dependency())],
) -> dict:
    return {
        "sub": oauth_user.sub,
        "email": oauth_user.email,
        "name": oauth_user.name,
    }
```

## GitHub example

Use `GitHubOAuthClient` and `GithubUser` instead.
The code is almost the same - just change the class names.

## Storage Options

### MemoryStore

Stores state in process memory. Good for development and single-instance apps.

```python
from dead_simple_oauth_fastapi import MemoryStore

store = MemoryStore()
```

> ![WARNING]
> Do not use MemoryStore if your app runs on multiple servers — each server has its own memory and the state stored on one will not be visible to another.

### RedisStore

Stores state in Redis. Use this for multi-instance or load-balanced deployments.

```python
from redis.asyncio import Redis
from dead_simple_oauth_fastapi import RedisStore

redis = Redis.from_url("redis://localhost:6379", decode_responses=True)
store = RedisStore(redis)
```

You can also customize the key prefix:

```python
store = RedisStore(redis, prefix="users:oauth:")
```

## Custom Error Messages (optional)

All user-facing error messages can be overridden. You do not have to replace all of them - only what you need.

```python
from dead_simple_oauth_fastapi import OAuthMessages

messages = OAuthMessages(
    missing_code="Authorization code is missing.",
    invalid_or_expired_state="Login session expired. Please try again.",
)

google = GoogleOAuthClient(..., messages=messages)
```

You only need to change the messages you want.

## What is `state` and `PKCE`?

These are two security mechanisms this package uses automatically. You do not need to configure them - but understanding what they do helps.

### `state` - protecting against fake callbacks. (client side)

When your app starts the oauth flow, it generates a random string called `state` and saves it in storage options you choise a short-lived cookie on the user's browser and send it in the url params.
When Google or GitHub redirects the user back to your callback URL, they include that same `state` value in the URL. Your app compares the two.

If they match → the callback is valid, request and response legitimately originated in the same browser.
If they do not match → someone sent a forged callback, reject it.

**Why does this matter?** Without it, an attacker could craft a fake callback URL and trick your app into processing it. The `state` check ensures your app only accepts callbacks it actually initiated or originated.

### `PKCE` - protecting the authorization code(authorization server side)

After the user logs in, Google or GitHub sends an authorization code back to your callback URL. Your app then exchanges this code for an access token.

The problem: this code travels through the browser (in a URL), and could theoretically be stolen.

`PKCE` (Proof Key for Code Exchange, pronounced "pixy") protects against this. Before the redirect, your app generates two related values:

- `code_verifier` — a random secret, kept private
- `code_challenge` — a hash of the verifier, sent to the provider

When your app later exchanges the `code` for a token, it sends the original `code_verifier`. The provider hashes it and checks it against the `code_challenge` it stored earlier.

Even if an attacker steals the `authorization code`, they do not have the `code_verifier`. The provider will reject their token request.

This package handles both `state` and `PKCE` for every request automatically!
