Skip to content

Authentication

Vibetuner provides built-in authentication with OAuth and magic links.

Overview

Vibetuner includes:

  • OAuth Authentication: Google, GitHub, and more via Authlib
  • Magic Link Authentication: Passwordless email-based login
  • Session Management: Secure cookie-based sessions
  • User Model: Pre-configured with OAuth account linking

OAuth Setup

Google OAuth

  1. Go to Google Cloud Console
  2. Create a new project or select existing
  3. Enable Google+ API
  4. Create OAuth 2.0 credentials
  5. Add authorized redirect URI: http://localhost:8000/auth/google/callback

Add to .env:

GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

GitHub OAuth

  1. Go to GitHub Developer Settings
  2. Create a new OAuth App
  3. Set callback URL: http://localhost:8000/auth/github/callback

Add to .env:

GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret

Adding More Providers

Vibetuner uses Authlib which supports many OAuth providers. Edit src/vibetuner/frontend/auth.py to add providers:

# Example: Adding Discord
oauth.register(
name="discord",
client_id=settings.DISCORD_CLIENT_ID,
client_secret=settings.DISCORD_CLIENT_SECRET,
authorize_url="https://discord.com/api/oauth2/authorize",
access_token_url="https://discord.com/api/oauth2/token",
userinfo_url="https://discord.com/api/users/@me",
client_kwargs={"scope": "identify email"},
)

Magic links provide passwordless authentication via email.

Configuration

Magic links are enabled by default. Configure email settings:

# .env
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=[email protected]
SMTP_PASSWORD=your-app-password
FROM_EMAIL=[email protected]

How It Works

  1. User enters email address
  2. System sends email with unique token
  3. User clicks link in email
  4. System validates token and logs user in
  5. Session created

Customizing Email Templates

Edit templates/emails/magic_link.html.jinja:

<!DOCTYPE html>
<html>
    <body>
        <h1>Sign in to {{ app_name }}</h1>
        <p>Click the link below to sign in:</p>
        <a href="{{ magic_link }}">Sign In</a>
        <p>This link expires in 15 minutes.</p>
    </body>
</html>

User Model

The built-in User model supports both OAuth and magic link authentication:

# src/vibetuner/models/user.py
class User(Document):
email: str
name: str | None
avatar_url: str | None
oauth_accounts: list[OAuthAccount] = []
class Settings:
name = "users"

Extending the User Model

Create your own model that extends the base:

# src/app/models/user.py
from vibetuner.models import User as BaseUser
from pydantic import Field
class User(BaseUser):
bio: str | None = None
preferences: dict = Field(default_factory=dict)
class Settings:
name = "users"

Session Management

Sessions use secure, HTTP-only cookies:

# Default session configuration
SESSION_MAX_AGE = 60 * 60 * 24 * 30  # 30 days
SESSION_COOKIE_NAME = "session"
SESSION_SECRET_KEY = settings.SECRET_KEY

Custom Session Configuration

Edit src/vibetuner/config.py:

class Settings(BaseSettings):
SESSION_MAX_AGE: int = 60 * 60 * 24 * 7  # 7 days
SESSION_COOKIE_SECURE: bool = True  # HTTPS only
SESSION_COOKIE_SAMESITE: str = "lax"

Protecting Routes

Require Authentication

Use the @require_auth decorator:

from vibetuner.frontend.auth import require_auth
@router.get("/dashboard")
@require_auth
async def dashboard(request: Request):
user = request.state.user
return templates.TemplateResponse("dashboard.html.jinja", {
"user": user
})

Optional Authentication

Access user if authenticated, but don't require it:

@router.get("/")
async def home(request: Request):
user = getattr(request.state, "user", None)
return templates.TemplateResponse("home.html.jinja", {
"user": user
})

Template Context

User is automatically available in templates:

{% if user %}
    <p>Welcome, {{ user.name }}!</p>
    <a href="/auth/logout">Logout</a>
{% else %}
    <a href="/auth/google">Sign in with Google</a>
    <a href="/auth/magic-link">Sign in with Email</a>
{% endif %}

Custom Authentication Logic

After Login Hook

Customize what happens after successful authentication:

# src/app/frontend/hooks.py
async def on_user_login(user: User, request: Request):
# Log login event
# Update last_login timestamp
# Send welcome email
pass

Register in src/app/frontend/__init__.py:

from vibetuner.frontend import events
from app.frontend.hooks import on_user_login
events.register("user_login", on_user_login)

Security Considerations

HTTPS in Production

Always use HTTPS in production:

# Production settings
SESSION_COOKIE_SECURE = True  # HTTPS only
SESSION_COOKIE_SAMESITE = "lax"  # CSRF protection

Secret Key

Use a strong, unique secret key:

# Generate a secure key
python -c "import secrets; print(secrets.token_urlsafe(32))"

Add to .env:

SECRET_KEY=your-generated-secret-key

OAuth Callback URLs

Use exact URLs in OAuth provider settings:

Development: http://localhost:8000/auth/google/callback
Production: https://example.com/auth/google/callback

Rate Limiting

Consider adding rate limiting for authentication endpoints:

from slowapi import Limiter
limiter = Limiter(key_func=lambda: request.client.host)
@router.post("/auth/magic-link")
@limiter.limit("5/minute")
async def request_magic_link(email: str):
# Send magic link
pass

Troubleshooting

OAuth Redirect Mismatch

Ensure callback URLs exactly match in:

  1. OAuth provider settings
  2. Your .env configuration
  3. The URL used in production

Check:

  1. SMTP settings are correct
  2. Email is being sent (check logs)
  3. Token hasn't expired (15 minutes default)
  4. Email isn't in spam folder

Session Expires Too Quickly

Increase session duration:

SESSION_MAX_AGE = 60 * 60 * 24 * 90  # 90 days

Next Steps