Skip to content

Authentication System

Complete authentication with JWT tokens, Google OAuth, and password reset.

Features

Email/Password Authentication

  • JWT token-based auth
  • Bcrypt password hashing
  • HTTP-only cookie storage
  • 7-day token expiration (configurable)

Google OAuth

  • One-click Google sign-in
  • Automatic account creation
  • Secure OAuth 2.0 flow

Password Reset

  • Email-based reset flow
  • Secure token generation
  • 1-hour token expiration
  • Email notifications

Session Management

  • Automatic token refresh
  • Secure logout
  • Protected routes
  • Authentication persistence

Quick Start

Create First User

Bash
1
2
3
4
5
6
7
8
# Create superuser (Top G+)
task db:user-create -- \
  --email [email protected] \
  --password securepass123 \
  --full_name "John Doe"

# Or use signup page
# Visit: http://localhost:5173/signup

Test Authentication

Bash
1
2
3
4
5
6
# Login via API
curl -X POST http://localhost:8020/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"securepass123"}'

# Returns JWT token

Configuration

Environment Variables

Text Only
# local.env

# JWT Configuration
secret_key=your-secret-key-min-32-chars
algorithm=HS256
access_token_expire_minutes=10080  # 7 days

# Google OAuth (optional)
google_oauth2_client_id=your-client-id
google_oauth2_secret=your-secret
google_oauth2_redirect_uri=http://localhost:8020/api/auth/google_callback

Google OAuth Setup

  1. Go to Google Cloud Console
  2. Create project
  3. Enable Google+ API
  4. Create OAuth 2.0 credentials
  5. Add authorized redirect URI: http://localhost:8020/api/auth/google_callback
  6. Copy Client ID and Secret to local.env

Backend Implementation

Controllers

Login (POST /api/auth/login):

Python
1
2
3
4
5
# app/controllers/auth.py
@auth_router.post("/login")
async def login(form_data: LoginForm):
    user = await user_service.login(form_data.email, form_data.password)
    # Returns JWT token in cookie

Signup (POST /api/auth/signup):

Python
1
2
3
4
@auth_router.post("/signup")
async def signup(form_data: SignupForm):
    user = await user_service.create_user(form_data)
    # Auto-login after signup

Google OAuth (GET /api/auth/google/authorize):

Python
1
2
3
@auth_router.get("/google/authorize")
async def google_authorize():
    # Redirects to Google OAuth

Services

User Authentication (app/services/users_service.py):

Python
class UserService:
    async def authenticate_user(self, email: str, password: str):
        user = await self.get_user_by_email(email)
        if not user or not self.verify_password(password, user.password_hash):
            raise HTTPException(401, "Invalid credentials")
        return user

    async def login(self, email: str, password: str):
        user = await self.authenticate_user(email, password)
        token = self.create_access_token({"sub": str(user.id)})
        return {"access_token": token}

Frontend Implementation

Login Page

TypeScript
// frontend/src/pages/Login.tsx
import { useLogin } from '@/hooks/api/useAuth';

const Login = () => {
  const login = useLogin();

  const handleSubmit = (data: LoginForm) => {
    login.mutate(data, {
      onSuccess: () => navigate('/dashboard')
    });
  };

  return <LoginForm onSubmit={handleSubmit} />;
};

Protected Routes

TypeScript
1
2
3
4
5
6
7
8
9
// frontend/src/components/ProtectedRoute.tsx
const ProtectedRoute = () => {
  const { data: user, isLoading } = useCurrentUser();

  if (isLoading) return <Loading />;
  if (!user) return <Navigate to="/login" />;

  return <Outlet />;
};

Google OAuth Button

TypeScript
1
2
3
4
5
6
7
const GoogleLogin = () => {
  const handleGoogleLogin = () => {
    window.location.href = 'http://localhost:8020/api/auth/google/authorize';
  };

  return <Button onClick={handleGoogleLogin}>Sign in with Google</Button>;
};

Security

Password Requirements

  • Minimum 8 characters (configurable)
  • Bcrypt hashing with salt
  • Never stored in plain text

JWT Tokens

  • Signed with secret key
  • Stored in HTTP-only cookies
  • Not accessible via JavaScript
  • Automatic expiration

OAuth Security

  • State parameter validation
  • PKCE flow support
  • Secure token exchange

Common Issues

Problem: Google OAuth redirect fails
Solution: Verify redirect URI matches exactly:

Text Only
google_oauth2_redirect_uri=http://localhost:8020/api/auth/google_callback

Problem: JWT token expired
Solution: User must login again, or implement refresh token

Problem: Password reset email not sending
Solution: Configure Mailchimp API key in local.env

API Endpoints

  • POST /api/auth/login - Email/password login
  • POST /api/auth/signup - Create account
  • GET /api/auth/logout - Logout user
  • GET /api/auth/current - Get current user
  • GET /api/auth/google/authorize - Google OAuth start
  • GET /api/auth/google_callback - Google OAuth callback
  • POST /api/auth/forgot-password - Request password reset
  • POST /api/auth/reset-password - Reset password with token
  • PUT /api/auth/profile - Update user profile

Full API reference

Files Reference

  • app/controllers/auth.py - Auth endpoints
  • app/services/users_service.py - User logic
  • app/services/oauth_service.py - OAuth logic
  • app/auth_backend.py - JWT handling
  • frontend/src/pages/Login.tsx - Login page
  • frontend/src/pages/SignUp.tsx - Signup page
  • frontend/src/hooks/api/useAuth.ts - Auth hooks