mitlist/be/app/api/auth/oauth.py
mohamad 84b046508a feat: Implement refresh token functionality in authentication flow
- Added support for refresh tokens in the authentication backend, allowing users to obtain new access tokens using valid refresh tokens.
- Created a new `BearerResponseWithRefresh` model to structure responses containing both access and refresh tokens.
- Updated the `AuthenticationBackend` to handle login and logout processes with refresh token support.
- Introduced a new `/auth/jwt/refresh` endpoint to facilitate token refreshing, validating the refresh token and generating new tokens as needed.
- Modified OAuth callback logic to generate and return both access and refresh tokens upon successful authentication.
- Updated frontend API service to send the refresh token in the Authorization header for token refresh requests.
2025-05-25 12:51:02 +02:00

95 lines
4.0 KiB
Python

from fastapi import APIRouter, Depends, Request
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_transactional_session
from app.models import User
from app.auth import oauth, fastapi_users, auth_backend, get_jwt_strategy, get_refresh_jwt_strategy
from app.config import settings
router = APIRouter()
@router.get('/google/login')
async def google_login(request: Request):
return await oauth.google.authorize_redirect(request, settings.GOOGLE_REDIRECT_URI)
@router.get('/google/callback')
async def google_callback(request: Request, db: AsyncSession = Depends(get_transactional_session)):
token_data = await oauth.google.authorize_access_token(request)
user_info = await oauth.google.parse_id_token(request, token_data)
# Check if user exists
existing_user = (await db.execute(select(User).where(User.email == user_info['email']))).scalar_one_or_none()
user_to_login = existing_user
if not existing_user:
# Create new user
new_user = User(
email=user_info['email'],
name=user_info.get('name', user_info.get('email')),
is_verified=True, # Email is verified by Google
is_active=True
)
db.add(new_user)
await db.flush() # Use flush instead of commit since we're in a transaction
user_to_login = new_user
# Generate JWT tokens using the new backend
access_strategy = get_jwt_strategy()
refresh_strategy = get_refresh_jwt_strategy()
access_token = await access_strategy.write_token(user_to_login)
refresh_token = await refresh_strategy.write_token(user_to_login)
# Redirect to frontend with tokens
redirect_url = f"{settings.FRONTEND_URL}/auth/callback?access_token={access_token}&refresh_token={refresh_token}"
return RedirectResponse(url=redirect_url)
@router.get('/apple/login')
async def apple_login(request: Request):
return await oauth.apple.authorize_redirect(request, settings.APPLE_REDIRECT_URI)
@router.get('/apple/callback')
async def apple_callback(request: Request, db: AsyncSession = Depends(get_transactional_session)):
token_data = await oauth.apple.authorize_access_token(request)
user_info = token_data.get('user', await oauth.apple.userinfo(token=token_data) if hasattr(oauth.apple, 'userinfo') else {})
if 'email' not in user_info and 'sub' in token_data:
parsed_id_token = await oauth.apple.parse_id_token(request, token_data) if hasattr(oauth.apple, 'parse_id_token') else {}
user_info = {**parsed_id_token, **user_info}
if 'email' not in user_info:
return RedirectResponse(url=f"{settings.FRONTEND_URL}/auth/callback?error=apple_email_missing")
# Check if user exists
existing_user = (await db.execute(select(User).where(User.email == user_info['email']))).scalar_one_or_none()
user_to_login = existing_user
if not existing_user:
# Create new user
name_info = user_info.get('name', {})
first_name = name_info.get('firstName', '')
last_name = name_info.get('lastName', '')
full_name = f"{first_name} {last_name}".strip() if first_name or last_name else user_info.get('email')
new_user = User(
email=user_info['email'],
name=full_name,
is_verified=True, # Email is verified by Apple
is_active=True
)
db.add(new_user)
await db.flush() # Use flush instead of commit since we're in a transaction
user_to_login = new_user
# Generate JWT tokens using the new backend
access_strategy = get_jwt_strategy()
refresh_strategy = get_refresh_jwt_strategy()
access_token = await access_strategy.write_token(user_to_login)
refresh_token = await refresh_strategy.write_token(user_to_login)
# Redirect to frontend with tokens
redirect_url = f"{settings.FRONTEND_URL}/auth/callback?access_token={access_token}&refresh_token={refresh_token}"
return RedirectResponse(url=redirect_url)