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¶
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
http://localhost:8000/auth/google/callback
Add to .env:
GitHub OAuth¶
- Go to GitHub Developer Settings
- Create a new OAuth App
- Set callback URL:
http://localhost:8000/auth/github/callback
Add to .env:
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 Link Authentication¶
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¶
- User enters email address
- System sends email with unique token
- User clicks link in email
- System validates token and logs user in
- 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:
Add to .env:
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:
- OAuth provider settings
- Your
.envconfiguration - The URL used in production
Magic Links Not Working¶
Check:
- SMTP settings are correct
- Email is being sent (check logs)
- Token hasn't expired (15 minutes default)
- Email isn't in spam folder
Session Expires Too Quickly¶
Increase session duration:
Next Steps¶
- Development Guide - Build features
- Deployment - Deploy to production