Skip to content

Instantly share code, notes, and snippets.

@SyNeto
Last active November 8, 2025 10:52
Show Gist options
  • Select an option

  • Save SyNeto/585025034e8dfa4a7421f99f38385587 to your computer and use it in GitHub Desktop.

Select an option

Save SyNeto/585025034e8dfa4a7421f99f38385587 to your computer and use it in GitHub Desktop.
Auth Provider Implementation Guide - MicroCommerce SDK

Auth Provider Implementation Guide

Branch: feat/auth-service-auth-provider-interface Goal: Implement AuthProvider interface in SDK as infrastructure


Overview

We're creating the authentication abstraction layer in the SDK, following the same pattern as DatabaseConnector. This is infrastructure code that other services will consume.


Architecture Diagram

graph TB
    subgraph "SDK Infrastructure Layer"
        A[AuthProvider Interface<br/>Abstract Base Class]
        B[Auth Exceptions<br/>Custom Error Types]
    end

    subgraph "Future Implementations - Not in this branch"
        C[FirebaseAuthProvider]
        D[OAuthIntrospectionProvider]
        E[LocalAuthProvider]
    end

    subgraph "Services - Consumer Layer"
        F[auth_service Package<br/>Future]
    end

    C -.implements.-> A
    D -.implements.-> A
    E -.implements.-> A

    F -.uses.-> A
    F -.uses.-> B

    style A fill:#e1f5ff
    style B fill:#e1f5ff
    style C fill:#f0f0f0
    style D fill:#f0f0f0
    style E fill:#f0f0f0
    style F fill:#f0f0f0
Loading

This branch only creates the blue boxes - the interface and exceptions.


Interface Design

AuthProvider Abstract Class

# Location: src/microcommerce_sdk/auth/interface.py

from abc import ABC, abstractmethod
from typing import Optional

class AuthProvider(ABC):
    """
    Abstract authentication provider interface.

    This interface defines the contract for authentication operations
    that all providers must implement. Enables swappable auth strategies.
    """

    @abstractmethod
    async def authenticate(self, credentials: dict) -> tuple[dict, str]:
        """
        Authenticate user with provided credentials.

        Args:
            credentials: Dict with auth data
                For email/password: {"email": str, "password": str}
                For token: {"token": str}
                For OAuth: {"code": str, "redirect_uri": str}

        Returns:
            Tuple of (user_data, token)
            - user_data: Dict with user info from auth system
              Example: {"id": "auth_123", "email": "[email protected]", ...}
            - token: JWT or session token string

        Raises:
            AuthenticationError: Invalid credentials
            AuthProviderError: Auth system unavailable

        Example:
            user_data, token = await provider.authenticate({
                "email": "[email protected]",
                "password": "secret123"
            })
        """
        pass

    @abstractmethod
    async def validate_token(self, token: str) -> Optional[dict]:
        """
        Validate authentication token and extract user data.

        Args:
            token: JWT or session token to validate

        Returns:
            User data dict if valid, None if invalid/expired
            Example: {"id": "auth_123", "email": "[email protected]"}

        Raises:
            AuthProviderError: Auth system unavailable

        Example:
            user_data = await provider.validate_token("eyJhbG...")
            if user_data:
                print(f"Valid token for: {user_data['email']}")
        """
        pass

    @abstractmethod
    async def refresh_token(self, refresh_token: str) -> str:
        """
        Generate new access token from refresh token.

        Args:
            refresh_token: Valid refresh token

        Returns:
            New access token string

        Raises:
            AuthenticationError: Refresh token invalid/expired
            AuthProviderError: Auth system unavailable

        Example:
            new_token = await provider.refresh_token(old_refresh_token)
        """
        pass

    @abstractmethod
    async def revoke_token(self, token: str) -> bool:
        """
        Revoke/invalidate an authentication token.

        Args:
            token: Token to revoke

        Returns:
            True if revoked successfully, False if already invalid

        Raises:
            AuthProviderError: Auth system unavailable

        Example:
            success = await provider.revoke_token(user_token)
        """
        pass

    @abstractmethod
    async def create_user(self, user_data: dict) -> tuple[str, str]:
        """
        Create new user in authentication system.

        Args:
            user_data: User registration data
                Required: {"email": str, "password": str}
                Optional: {"display_name": str, ...}

        Returns:
            Tuple of (auth_user_id, initial_token)
            - auth_user_id: Unique ID in auth system
            - initial_token: JWT/session token for immediate login

        Raises:
            ValidationError: User data invalid
            UserExistsError: User already exists
            AuthProviderError: Auth system unavailable

        Example:
            auth_id, token = await provider.create_user({
                "email": "[email protected]",
                "password": "secure123",
                "display_name": "New User"
            })
        """
        pass

Exception Hierarchy

graph TB
    A[Exception<br/>Python built-in]
    B[AuthProviderError<br/>Base auth exception]
    C[AuthenticationError<br/>Invalid credentials]
    D[TokenExpiredError<br/>Token expired]
    E[UserExistsError<br/>Duplicate user]
    F[ValidationError<br/>Invalid data]

    A --> B
    B --> C
    B --> E
    B --> F
    C --> D

    style A fill:#f0f0f0
    style B fill:#ffe1e1
    style C fill:#fff4e1
    style D fill:#fff4e1
    style E fill:#fff4e1
    style F fill:#fff4e1
Loading

Exception Definitions

# Location: src/microcommerce_sdk/auth/exceptions.py

class AuthProviderError(Exception):
    """
    Base exception for all auth provider errors.

    Attributes:
        message: Human-readable error description
        provider: Name of the auth provider (optional)
    """

    def __init__(self, message: str, provider: str = None):
        self.message = message
        self.provider = provider
        super().__init__(self.message)


class AuthenticationError(AuthProviderError):
    """
    Raised when authentication fails.

    Common causes:
    - Invalid email/password
    - Invalid token
    - Account disabled
    """
    pass


class TokenExpiredError(AuthenticationError):
    """
    Raised when token has expired.

    Client should refresh token or re-authenticate.
    """
    pass


class UserExistsError(AuthProviderError):
    """
    Raised when attempting to create duplicate user.

    Typically occurs when email already registered.
    """
    pass


class ValidationError(AuthProviderError):
    """
    Raised when user data fails validation.

    Attributes:
        validation_errors: List of specific validation failures
    """

    def __init__(self, message: str, validation_errors: list[str] = None):
        super().__init__(message)
        self.validation_errors = validation_errors or []

Authentication Flow Examples

Flow 1: User Registration

sequenceDiagram
    participant Service as AuthService
    participant Provider as AuthProvider
    participant AuthSystem as Auth System

    Service->>Provider: create_user(email, password)
    Provider->>AuthSystem: Create user account

    alt Success
        AuthSystem-->>Provider: auth_user_id
        Provider->>AuthSystem: Generate initial token
        AuthSystem-->>Provider: token
        Provider-->>Service: (auth_user_id, token)
    else User Exists
        AuthSystem-->>Provider: Error: Email exists
        Provider-->>Service: Raise UserExistsError
    else Invalid Data
        AuthSystem-->>Provider: Error: Invalid format
        Provider-->>Service: Raise ValidationError
    end
Loading

Flow 2: User Login

sequenceDiagram
    participant Service as AuthService
    participant Provider as AuthProvider
    participant AuthSystem as Auth System

    Service->>Provider: authenticate(email, password)
    Provider->>AuthSystem: Verify credentials

    alt Valid Credentials
        AuthSystem-->>Provider: User verified
        Provider->>AuthSystem: Generate token
        AuthSystem-->>Provider: token + user_data
        Provider-->>Service: (user_data, token)
    else Invalid Credentials
        AuthSystem-->>Provider: Error: Invalid
        Provider-->>Service: Raise AuthenticationError
    end
Loading

Flow 3: Token Validation (API Request)

sequenceDiagram
    participant API as API Endpoint
    participant Provider as AuthProvider
    participant AuthSystem as Auth System

    API->>Provider: validate_token(token)
    Provider->>AuthSystem: Verify token signature

    alt Valid Token
        AuthSystem-->>Provider: Token valid + user_data
        Provider-->>API: user_data dict
        API->>API: Process request
    else Expired Token
        AuthSystem-->>Provider: Token expired
        Provider-->>API: None
        API->>API: Return 401 Unauthorized
    else Invalid Token
        AuthSystem-->>Provider: Invalid signature
        Provider-->>API: None
        API->>API: Return 401 Unauthorized
    end
Loading

Files to Create

1. Package Structure

packages/microcommerce_sdk/src/microcommerce_sdk/auth/
├── __init__.py           # Package exports
├── interface.py          # AuthProvider abstract class
└── exceptions.py         # Auth exceptions

2. __init__.py - Package Exports

"""
Authentication provider infrastructure for MicroCommerce SDK.

This module provides the abstract interface for authentication providers,
enabling swappable authentication strategies across different services.

See ADR-003 for design decisions and rationale.
"""

from .interface import AuthProvider
from .exceptions import (
    AuthProviderError,
    AuthenticationError,
    TokenExpiredError,
    UserExistsError,
    ValidationError,
)

__all__ = [
    # Interface
    "AuthProvider",

    # Exceptions
    "AuthProviderError",
    "AuthenticationError",
    "TokenExpiredError",
    "UserExistsError",
    "ValidationError",
]

Testing Strategy

Test Coverage Areas

  1. Interface Compliance Tests

    • Verify abstract methods exist
    • Check method signatures
    • Ensure proper inheritance
  2. Exception Tests

    • Exception hierarchy
    • Exception attributes
    • Error messages
  3. Mock Provider Tests

    • Create mock implementation for testing
    • Verify all methods are called correctly
    • Test error scenarios

Test File Structure

packages/microcommerce_sdk/tests/
├── test_auth_provider_interface.py    # Interface tests
└── test_auth_exceptions.py            # Exception tests

Key Design Principles

1. Interface-First Design

We define the contract before any implementation. This ensures:

  • All providers have consistent API
  • Services can depend on stable interface
  • Easy to add new providers later

2. Async-First

All methods are async because authentication typically involves:

  • Network calls to auth systems
  • Database queries
  • Token generation/validation

3. Provider Agnostic

The interface doesn't assume any specific auth system:

  • No Firebase-specific methods
  • No OAuth-specific parameters
  • Generic credentials dict for flexibility

4. Separation of Concerns

AuthProvider handles:

  • Authentication (verify credentials)
  • Token lifecycle (create, validate, refresh, revoke)
  • User creation in auth system

AuthProvider does NOT handle:

  • User profile data (belongs to repositories)
  • Business logic (belongs to services)
  • Authorization/permissions (separate concern)

What This Branch Does NOT Include

Out of scope for this branch:

  • ❌ Firebase implementation (separate branch)
  • ❌ OAuth implementation (separate branch)
  • ❌ Service layer (auth_service package)
  • ❌ User models (auth_service package)
  • ❌ Integration tests (requires implementations)

Focus: Pure interface and exception definitions only.


Success Criteria

This branch is complete when:

✅ AuthProvider interface defined with all 5 methods ✅ All exception classes defined ✅ Package exports configured ✅ Interface compliance tests passing ✅ Exception tests passing ✅ Documentation complete ✅ Follows ADR-003 design


Next Steps After This Branch

After merging this branch to feat/auth-service-design:

  1. Create feat/auth-service-structure

    • Set up auth_service package
    • Create ADR-001 for service architecture
  2. Create feat/auth-service-firebase-provider

    • Implement FirebaseAuthProvider
    • First concrete implementation
  3. Create feat/auth-service-logic

    • AuthService and UserService
    • Business logic layer

Implementation Checklist

  • Create src/microcommerce_sdk/auth/ directory
  • Create interface.py with AuthProvider class
  • Create exceptions.py with exception hierarchy
  • Create __init__.py with exports
  • Create tests/test_auth_provider_interface.py
  • Create tests/test_auth_exceptions.py
  • Run tests - all passing
  • Update SDK __init__.py to export auth module
  • Commit with clear message
  • Merge to parent branch

Ready to implement? Let me know and I'll create these files!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment