fastapi-auth-plugin 0.1.5
A complete OAuth2 authentication plugin for FastAPI with user management, roles, and permissions
License: MIT
Requires Python: >=3.8
Keywords: fastapi, authentication, oauth2, jwt, authorization, rbac

# FastAPI Auth Plugin

A production-ready, plug-and-play authentication system for FastAPI applications with OAuth2 Password Flow, JWT tokens, role-based access control (RBAC), and PostgreSQL backend.

## Features

- **OAuth2 Password Flow** with JWT access and refresh tokens
- **Google OAuth Integration** - Sign in with Google (optional)
- **User Management** - Registration, login, profile management
- **Role-Based Access Control (RBAC)** - Flexible roles and permissions system
- **PostgreSQL Backend** with SQLAlchemy ORM
- **Secure Password Hashing** using Bcrypt
- **FastAPI Dependencies** for easy route protection
- **Granular Permissions** with resource:action pattern
- **Production Ready** with comprehensive error handling
- **Fully Typed** with Pydantic models
- **Extensible** - Easy to customize and extend

## Installation

```bash
pip install fastapi-auth-plugin
```

Or install from source:

```bash
cd fastapi-auth-plugin
pip install -e .
```

## Quick Start

### 1. Set up environment variables

Create a `.env` file:

```env
DATABASE_URL=postgresql://postgres:password@localhost:5432/your_db
SECRET_KEY=your-secret-key-here-change-in-production
```

Generate a secure secret key:

```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```

### 2. Integrate into your FastAPI app

```python
from fastapi import FastAPI, Depends
from fastapi_auth_plugin import auth_router, get_current_user, init_db, models

app = FastAPI()

# Initialize database on startup
@app.on_event("startup")
async def startup():
    init_db()

# Include authentication routes
app.include_router(auth_router, prefix="/api")

# Protected route example
@app.get("/protected")
def protected_route(user: models.User = Depends(get_current_user)):
    return {"message": f"Hello {user.username}!"}
```

### 3. Run your application

```bash
uvicorn main:app --reload
```

Visit `http://localhost:8000/docs` for interactive API documentation.

### 4. Default superuser password

On every application startup, `init_db()` ensures the `admin` superuser exists and generates a new random password.

The generated password is printed to the console at startup.

```
Username: admin
Password: <random-generated-at-startup>
```

**WARNING: this password rotates on each restart, so store it securely if you need it.**

## Google OAuth Setup (Optional)

The plugin supports Google OAuth for easier user authentication and registration.

### 1. Create Google OAuth Credentials

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the **Google+ API** (People API)
4. Go to **Credentials** → **Create Credentials** → **OAuth 2.0 Client ID**
5. Configure the OAuth consent screen
6. For Application type, select **Web application**
7. Add authorized redirect URIs:
   - `http://localhost:8000/api/auth/google/callback` (for web/API usage)
   - `http://localhost:9999/callback` (for Qt desktop client - **automated flow**)
   - `https://yourdomain.com/api/auth/google/callback` (production)
8. Copy your **Client ID** and **Client Secret**

### 2. Configure Environment Variables

Add to your `.env` file:

```env
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/api/auth/google/callback
```

### 3. Register OAuth Providers

Update your FastAPI app to register OAuth providers on startup:

```python
from fastapi import FastAPI
from fastapi_auth_plugin import auth_router, init_db, register_oauth_providers

app = FastAPI()

@app.on_event("startup")
async def startup():
    init_db()
    register_oauth_providers()  # Register Google OAuth

app.include_router(auth_router, prefix="/api")
```

### 4. Use Google Login

**Via API (Web Flow):**
1. Direct users to `GET /api/auth/google/login`
2. They'll be redirected to Google for authentication
3. After approval, Google redirects to `/api/auth/google/callback`
4. The callback returns JWT tokens in JSON format

**Via Qt Client (Device Flow - Recommended):**
- Click the "🔐 Login with Google" button
- Authenticate in your browser
- The tokens are automatically retrieved via polling
- **No manual copy/paste required!**

This uses a modern backend-mediated OAuth flow similar to VS Code, GitHub CLI, and other desktop applications.

### How Device Flow Works

1. Client calls `/auth/google/device/initiate` to get a unique state and auth URL
2. Client opens the auth URL in the user's browser
3. User authenticates with Google, which redirects to the backend
4. Backend stores JWT tokens associated with the state
5. Client polls `/auth/google/device/poll` with the state until tokens are ready
6. Client receives tokens automatically

This approach eliminates the need for local HTTP servers or redirect URI registration on arbitrary ports.

### Google OAuth Features

- **No password required** - Users authenticate with their Google account
- **Auto account creation** - New accounts are created automatically
- **Account linking** - Existing users can link their Google account
- **Profile sync** - User email and profile picture from Google
- **Same JWT tokens** - Works seamlessly with existing authentication flow
- **Desktop-friendly** - Modern device flow for desktop/CLI applications

### Google OAuth API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/auth/google/login` | Initiate Google OAuth flow (web) |
| GET | `/auth/google/callback` | Handle Google OAuth callback |
| GET | `/auth/google/device/initiate` | Initiate device/desktop OAuth flow |
| GET | `/auth/google/device/poll` | Poll for OAuth tokens (device flow) |
| POST | `/auth/google/exchange` | Exchange OAuth code for tokens (legacy) |

## Usage Examples

### Protect Routes with Authentication

```python
from fastapi import Depends
from fastapi_auth_plugin import get_current_user, models

@app.get("/profile")
def get_profile(user: models.User = Depends(get_current_user)):
    return {
        "username": user.username,
        "email": user.email,
        "roles": [role.name for role in user.roles]
    }
```

### Require Specific Roles

```python
from fastapi_auth_plugin import require_role

@app.get("/admin/dashboard")
def admin_dashboard(user = Depends(require_role("admin", "superuser"))):
    return {"message": "Admin access granted"}
```

### Require Specific Permissions

```python
from fastapi_auth_plugin import require_permission

@app.delete("/posts/{post_id}")
def delete_post(
    post_id: int,
    user = Depends(require_permission("posts", "delete"))
):
    return {"message": f"Post {post_id} deleted"}
```

### Optional Authentication

```python
from fastapi_auth_plugin import optional_user

@app.get("/posts")
def list_posts(user = Depends(optional_user)):
    if user:
        # Show personalized content
        return {"posts": [...], "user": user.username}
    else:
        # Show public content
        return {"posts": [...]}
```

## API Endpoints

### Authentication

| Method | Endpoint | Description | Auth Required |
|--------|----------|-------------|---------------|
| POST | `/auth/register` | Register new user | No |
| POST | `/auth/login` | Login (OAuth2) | No |
| POST | `/auth/refresh` | Refresh access token | No |
| POST | `/auth/logout` | Logout | Yes |
| GET | `/auth/me` | Get current user profile | Yes |
| PUT | `/auth/me` | Update current user | Yes |

### User Management (Admin)

| Method | Endpoint | Description | Permission |
|--------|----------|-------------|------------|
| GET | `/auth/users` | List all users | `users:read` |
| GET | `/auth/users/{id}` | Get user by ID | `users:read` |
| PUT | `/auth/users/{id}` | Update user | `users:write` |
| DELETE | `/auth/users/{id}` | Delete user | `users:delete` |
| POST | `/auth/users/{id}/roles` | Assign roles | `users:write` |

### Role Management

| Method | Endpoint | Description | Permission |
|--------|----------|-------------|------------|
| GET | `/auth/roles` | List all roles | `roles:read` |
| POST | `/auth/roles` | Create role | Superuser |
| PUT | `/auth/roles/{id}` | Update role | Superuser |
| DELETE | `/auth/roles/{id}` | Delete role | Superuser |

### Permissions

| Method | Endpoint | Description | Permission |
|--------|----------|-------------|------------|
| GET | `/auth/permissions` | List all permissions | `permissions:read` |
| POST | `/auth/permissions` | Create permission | Superuser |

## Configuration

All settings can be configured via environment variables:

```python
from fastapi_auth_plugin import AuthSettings

# Custom configuration
settings = AuthSettings(
    DATABASE_URL="postgresql://user:pass@localhost/db",
    SECRET_KEY="your-secret-key",
    ACCESS_TOKEN_EXPIRE_MINUTES=30,
    REFRESH_TOKEN_EXPIRE_DAYS=7,
    ALGORITHM="HS256"
)
```

### Available Settings

| Variable | Default | Description |
|----------|---------|-------------|
| `DATABASE_URL` | `postgresql://...` | PostgreSQL connection URL |
| `SECRET_KEY` | (required) | Secret key for JWT signing |
| `ALGORITHM` | `HS256` | JWT algorithm |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `30` | Access token lifetime |
| `REFRESH_TOKEN_EXPIRE_DAYS` | `7` | Refresh token lifetime |
| `TOKEN_URL` | `/auth/login` | OAuth2 token endpoint |
| `GOOGLE_CLIENT_ID` | None | Google OAuth client ID (optional) |
| `GOOGLE_CLIENT_SECRET` | None | Google OAuth client secret (optional) |
| `GOOGLE_REDIRECT_URI` | `http://localhost:8000/api/auth/google/callback` | Google OAuth redirect URI |

## Database Schema

### User Model

- `id`: int (primary key)
- `email`: str (unique)
- `username`: str (unique)
- `hashed_password`: str (nullable for OAuth users)
- `is_active`: bool
- `is_superuser`: bool
- `google_id`: str (nullable, unique - Google OAuth user ID)
- `google_picture`: str (nullable - Google profile picture URL)
- `oauth_provider`: str (nullable - 'google', 'github', etc.)
- `created_at`: datetime
- `updated_at`: datetime
- `roles`: list[Role] (many-to-many)

### Role Model

- `id`: int (primary key)
- `name`: str (unique)
- `description`: str
- `permissions`: list[Permission] (many-to-many)
- `users`: list[User] (many-to-many)

### Permission Model

- `id`: int (primary key)
- `name`: str (unique, e.g., "users:read")
- `resource`: str (e.g., "users")
- `action`: str (e.g., "read")
- `description`: str
- `roles`: list[Role] (many-to-many)

## Default Roles & Permissions

The system creates three default roles on initialization:

### Superuser
- All permissions
- Full system access

### Admin
- `users:read`, `users:write`, `users:delete`
- `roles:read`

### User (default)
- `users:read` (own profile only)

## Running Tests

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# With coverage
pytest --cov=fastapi_auth_plugin --cov-report=html
```

## Security Best Practices

1. **Always use HTTPS in production**
2. **Store the startup-generated admin password securely (it rotates each restart)**
3. **Use strong, randomly generated SECRET_KEY**
4. **Set appropriate CORS policies**
5. **Implement rate limiting** (consider using `slowapi`)
6. **Use environment variables for sensitive data**
7. **Regularly update dependencies**
8. **Enable PostgreSQL SSL in production**
9. **Monitor and log authentication attempts**

## Example Application

See `examples/simple_app.py` for a complete working example showing:
- Database initialization
- Protected routes
- Role-based endpoints
- Permission-based endpoints
- Public and private content

Run the example:

```bash
cd examples
uvicorn simple_app:app --reload
```

## License

MIT License - see LICENSE file for details

## Changelog

### 0.1.0 (2024-01-06)
- Initial release
- OAuth2 Password Flow
- JWT authentication
- Role-based access control
- PostgreSQL backend
- Comprehensive test suite

## Credits

Built with:
- [FastAPI](https://fastapi.tiangolo.com/)
- [SQLAlchemy](https://www.sqlalchemy.org/)
- [Pydantic](https://pydantic-docs.helpmanual.io/)
- [python-jose](https://python-jose.readthedocs.io/)
- [passlib](https://passlib.readthedocs.io/)

- Publie :
    python -m build
  twine check dist/*
  twine upload dist/*