Metadata-Version: 2.4
Name: flaxon-oauth-google
Version: 0.1.2
Summary: Google OAuth 2.0 authentication plugin for Flaxon framework
Author-email: Aldane Hutchinson <aldanehutchinson5@gmail.com>
License: MIT
Project-URL: Homepage, https://flaxon-website.vercel.app/
Project-URL: Repository, https://github.com/aldanedev-create/flaxon-oauth-google
Project-URL: Issues, https://github.com/aldanedev-create/flaxon-oauth-google/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: flaxon>=0.1.7
Requires-Dist: httpx>=0.27.0
Requires-Dist: pyjwt>=2.8.0
Requires-Dist: cryptography>=42.0.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"
Requires-Dist: mypy>=1.8.0; extra == "dev"
Requires-Dist: pytest-httpx>=0.30.0; extra == "dev"
Provides-Extra: standard
Requires-Dist: flaxon[standard]; extra == "standard"
Dynamic: license-file

# Flaxon OAuth Google

<p align="center">
  <img src="https://raw.githubusercontent.com/aldanedev-create/Flaxon-Backend-Framework/main/assets/flaxon.png" alt="Flaxon Logo" width="200"/>
</p>

<p align="center">
  <a href="https://pypi.org/project/flaxon/"><img src="https://img.shields.io/pypi/v/flaxon.svg" alt="PyPI version"></a>
  <a href="https://github.com/aldanedev-create/Flaxon-Backend-Framework/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
  <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/badge/code%20style-ruff-000000.svg" alt="Code style: ruff"></a>
</p>

Google OAuth 2.0 authentication plugin for Flaxon framework.

## Table of Contents

* [Features](#features)
* [Installation](#installation)
* [Quick Start](#quick-start)
* [Configuration](#configuration)

  * [Environment Variables](#environment-variables)
  * [With Flaxon Config](#with-flaxon-config)
* [Advanced Usage](#advanced-usage)

  * [Custom Session Creation](#custom-session-creation)
  * [Custom User Mapping](#custom-user-mapping)
  * [Custom Scopes](#custom-scopes)
  * [Protected Routes](#protected-routes)
* [OAuth Endpoints](#oauth-endpoints)
* [Security Best Practices](#security-best-practices)
* [Example Application](#example-application)

  * [Complete Setup](#complete-setup)
* [Testing](#testing)
* [Requirements](#requirements)
* [Project Structure](#project-structure)
* [Roadmap](#roadmap)
* [Contributing](#contributing)
* [License](#license)
* [Support](#support)

## Features

* 🔐 **OAuth 2.0 Authorization Code Flow** — Full OAuth 2.0 implementation
* 👤 **User Info Retrieval** — Fetch user profile from Google's API
* 🔄 **Session Management** — Optional session creation after authentication
* 🛡️ **CSRF Protection** — State parameter validation
* ⚙️ **Configurable Scopes** — Request only the permissions you need
* 🧩 **User Mapping** — Map Google user data to your app's user model
* 🎯 **Error Handling** — Graceful OAuth error handling
* 🔁 **Refresh Tokens** — Automatic token refresh support

## Installation

```bash
pip install flaxon-oauth-google
```

## Quick Start

```python
from flaxon import Flaxon
from flaxon_oauth_google import GoogleOAuthPlugin

app = Flaxon("my-app")

# Basic usage with environment variables
app.plugins.load_plugin(GoogleOAuthPlugin(
    client_id="your-client-id.apps.googleusercontent.com",
    client_secret="your-client-secret",
    redirect_uri="https://yourapp.com/auth/google/callback",
))

@app.get("/")
async def home(request):
    return """
    <a href="/auth/google/login">Sign in with Google</a>
    """
```

## Configuration

### Environment Variables

```bash
# Required
export GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=your-client-secret

# Optional
export GOOGLE_REDIRECT_URI=https://yourapp.com/auth/google/callback
export GOOGLE_SCOPES=openid,email,profile
export GOOGLE_SUCCESS_REDIRECT=/dashboard
export GOOGLE_FAILURE_REDIRECT=/login?error=oauth_failed
```

### With Flaxon Config

```python
app = Flaxon("my-app", config={
    "GOOGLE_CLIENT_ID": "your-client-id",
    "GOOGLE_CLIENT_SECRET": "your-client-secret",
    "GOOGLE_REDIRECT_URI": "https://yourapp.com/auth/google/callback",
})

plugin = GoogleOAuthPlugin.from_config(app.config)
app.plugins.load_plugin(plugin)
```

## Advanced Usage

### Custom Session Creation

```python
def create_session_from_user(google_user):
    """Create a session from Google user data."""
    user = app_user_from_google(google_user)
    session_token = generate_session_token(user)
    return session_token

plugin = GoogleOAuthPlugin(
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    session_maker=create_session_from_user,
    success_redirect="/dashboard",
)
app.plugins.load_plugin(plugin)
```

### Custom User Mapping

```python
def map_google_user(google_user):
    """Map Google user to app user model."""
    return {
        "external_id": google_user.id,
        "email": google_user.email,
        "email_verified": google_user.verified_email,
        "full_name": google_user.name,
        "first_name": google_user.given_name,
        "last_name": google_user.family_name,
        "avatar_url": google_user.picture,
        "locale": google_user.locale,
    }

plugin = GoogleOAuthPlugin(
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    user_mapper=map_google_user,
)
```

### Custom Scopes

```python
# Request only what you need
plugin = GoogleOAuthPlugin(
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    scopes=["openid", "email"],  # Just email, no profile
)

# Or request additional scopes
plugin = GoogleOAuthPlugin(
    client_id="...",
    client_secret="...",
    redirect_uri="...",
    scopes=[
        "openid",
        "email",
        "profile",
        "https://www.googleapis.com/auth/drive.readonly"
    ],
)
```

### Protected Routes

```python
from flaxon_oauth_google import login_required

@app.get("/profile")
@login_required
async def profile(request):
    user = request.session.get("user")
    return {
        "name": user.get("name"),
        "email": user.get("email"),
        "picture": user.get("picture"),
    }

@app.get("/settings")
@login_required
async def settings(request):
    return {"settings": "..."}
```

## OAuth Endpoints

| Route                   | Method | Description                       |
| ----------------------- | ------ | --------------------------------- |
| `/auth/google/login`    | GET    | Redirect to Google consent screen |
| `/auth/google/callback` | GET    | OAuth callback endpoint           |
| `/auth/google/logout`   | GET    | Clear session (optional)          |
| `/auth/google/user`     | GET    | Get current user info             |

## Security Best Practices

✅ Store client secret in environment variables

✅ Use HTTPS in production

✅ Validate redirect URI matches registered URI

✅ Use state parameter for CSRF protection

✅ Verify ID token signature

✅ Keep scopes minimal (least privilege)

✅ Regenerate session ID on login

✅ Use secure cookies (Secure, HttpOnly, SameSite)

## Example Application

### Complete Setup

```python
from flaxon import Flaxon
from flaxon_oauth_google import GoogleOAuthPlugin
import os

app = Flaxon("my-app")

# Load plugin
plugin = GoogleOAuthPlugin(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    redirect_uri=f"{os.environ['APP_URL']}/auth/google/callback",
    scopes=["openid", "email", "profile"],
    success_redirect="/dashboard",
    failure_redirect="/login?error=oauth_failed",
)

def create_session(google_user):
    """Create a session for the user."""
    # Your session creation logic
    session_id = f"session_{google_user.id}_{int(time.time())}"
    return session_id

plugin.session_maker = create_session
app.plugins.load_plugin(plugin)

@app.get("/")
async def home(request):
    return """
    <html>
        <body>
            <h1>Welcome to My App</h1>
            <a href="/auth/google/login">
                <button>Sign in with Google</button>
            </a>
        </body>
    </html>
    """

@app.get("/dashboard")
async def dashboard(request):
    user = request.session.get("user")
    if not user:
        return {"error": "Not authenticated"}, 401
    return {
        "welcome": f"Hello, {user.get('name')}!",
        "email": user.get("email"),
        "picture": user.get("picture"),
    }

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
```

## Testing

```bash
# Run tests
pytest

# Run with coverage
pytest --cov=flaxon_oauth_google

# Run specific test
pytest tests/test_provider.py -v
```

## Requirements

Python 3.11+

Flaxon 0.1.0+

httpx 0.27.0+

pyjwt 2.8.0+

cryptography 42.0.0+

## Project Structure

```text
flaxon-oauth-google/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│   └── flaxon_oauth_google/
│       ├── __init__.py       # Public API exports
│       ├── plugin.py         # GoogleOAuthPlugin class
│       ├── provider.py       # GoogleOAuthProvider
│       ├── client.py         # Google API client
│       ├── user.py           # User info mapping
│       └── routes.py         # OAuth route handlers
└── tests/
    ├── test_plugin.py
    ├── test_provider.py
    └── test_integration.py
```

## Roadmap

| Version | Features                            |
| ------- | ----------------------------------- |
| 0.1.0   | Basic Google OAuth flow             |
| 0.2.0   | G Suite domain restriction          |
| 0.3.0   | Service account support             |
| 0.4.0   | Social login buttons (HTML helpers) |
| 0.5.0   | Refresh token rotation              |

## Contributing

Fork the repository

Create a feature branch

Add tests for new features

Ensure all tests pass

Submit a pull request

## License

MIT License - See LICENSE file for details.

## Support

📚 Documentation

🐛 Issue Tracker

💬 Discussions
