Spaces:
Sleeping
Sleeping
File size: 7,470 Bytes
bcc8074 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
"""
Modular Google OAuth Service
A self-contained, plug-and-play service for verifying Google ID tokens.
Can be used in any Python application with minimal configuration.
Usage:
from services.google_auth_service import GoogleAuthService, GoogleUserInfo
# Initialize with client ID
auth_service = GoogleAuthService(client_id="your-google-client-id")
# Or use environment variable GOOGLE_CLIENT_ID
auth_service = GoogleAuthService()
# Verify a Google ID token
user_info = auth_service.verify_token(id_token)
print(user_info.email, user_info.google_id, user_info.name)
Environment Variables:
GOOGLE_CLIENT_ID: Your Google OAuth 2.0 Client ID
Dependencies:
google-auth>=2.0.0
google-auth-oauthlib>=1.0.0
"""
import os
import logging
from dataclasses import dataclass
from typing import Optional
from google.oauth2 import id_token as google_id_token
from google.auth.transport import requests as google_requests
logger = logging.getLogger(__name__)
@dataclass
class GoogleUserInfo:
"""
User information extracted from a verified Google ID token.
Attributes:
google_id: Unique Google user identifier (sub claim)
email: User's email address
email_verified: Whether Google has verified the email
name: User's display name (may be None)
picture: URL to user's profile picture (may be None)
given_name: User's first name (may be None)
family_name: User's last name (may be None)
locale: User's locale preference (may be None)
"""
google_id: str
email: str
email_verified: bool = True
name: Optional[str] = None
picture: Optional[str] = None
given_name: Optional[str] = None
family_name: Optional[str] = None
locale: Optional[str] = None
class GoogleAuthError(Exception):
"""Base exception for Google Auth errors."""
pass
class InvalidTokenError(GoogleAuthError):
"""Raised when the token is invalid or expired."""
pass
class ConfigurationError(GoogleAuthError):
"""Raised when the service is not properly configured."""
pass
class GoogleAuthService:
"""
Service for verifying Google OAuth ID tokens.
This service validates ID tokens issued by Google Sign-In and extracts
user information. It's designed to be modular and reusable across
different applications.
Example:
service = GoogleAuthService()
try:
user_info = service.verify_token(token_from_frontend)
print(f"Welcome {user_info.name}!")
except InvalidTokenError:
print("Invalid or expired token")
"""
def __init__(
self,
client_id: Optional[str] = None,
clock_skew_seconds: int = 0
):
"""
Initialize the Google Auth Service.
Args:
client_id: Google OAuth 2.0 Client ID. If not provided,
falls back to GOOGLE_CLIENT_ID environment variable.
clock_skew_seconds: Allowed clock skew in seconds for token
validation (default: 0).
Raises:
ConfigurationError: If no client_id is provided or found.
"""
self.client_id = client_id or os.getenv("AUTH_SIGN_IN_GOOGLE_CLIENT_ID")
self.clock_skew_seconds = clock_skew_seconds
if not self.client_id:
raise ConfigurationError(
"Google Client ID is required. Either pass client_id parameter "
"or set GOOGLE_CLIENT_ID environment variable."
)
logger.info(f"GoogleAuthService initialized with client_id: {self.client_id[:20]}...")
def verify_token(self, id_token: str) -> GoogleUserInfo:
"""
Verify a Google ID token and extract user information.
Args:
id_token: The ID token received from the frontend after
Google Sign-In.
Returns:
GoogleUserInfo: Dataclass containing user's Google profile info.
Raises:
InvalidTokenError: If the token is invalid, expired, or
doesn't match the expected client ID.
"""
if not id_token:
raise InvalidTokenError("Token cannot be empty")
try:
# Verify the token with Google
idinfo = google_id_token.verify_oauth2_token(
id_token,
google_requests.Request(),
self.client_id,
clock_skew_in_seconds=self.clock_skew_seconds
)
# Validate issuer
if idinfo.get("iss") not in ["accounts.google.com", "https://accounts.google.com"]:
raise InvalidTokenError("Invalid token issuer")
# Validate audience
if idinfo.get("aud") != self.client_id:
raise InvalidTokenError("Token was not issued for this application")
# Extract user info
return GoogleUserInfo(
google_id=idinfo["sub"],
email=idinfo["email"],
email_verified=idinfo.get("email_verified", False),
name=idinfo.get("name"),
picture=idinfo.get("picture"),
given_name=idinfo.get("given_name"),
family_name=idinfo.get("family_name"),
locale=idinfo.get("locale")
)
except ValueError as e:
logger.warning(f"Token verification failed: {e}")
raise InvalidTokenError(f"Token verification failed: {str(e)}")
except Exception as e:
logger.error(f"Unexpected error during token verification: {e}")
raise InvalidTokenError(f"Token verification error: {str(e)}")
def verify_token_safe(self, id_token: str) -> Optional[GoogleUserInfo]:
"""
Verify a Google ID token without raising exceptions.
Useful for cases where you want to check validity without
exception handling.
Args:
id_token: The ID token to verify.
Returns:
GoogleUserInfo if valid, None if invalid.
"""
try:
return self.verify_token(id_token)
except GoogleAuthError:
return None
# Singleton instance for convenience (initialized on first use)
_default_service: Optional[GoogleAuthService] = None
def get_google_auth_service() -> GoogleAuthService:
"""
Get the default GoogleAuthService instance.
Creates a singleton instance using environment variables.
Returns:
GoogleAuthService: The default service instance.
Raises:
ConfigurationError: If GOOGLE_CLIENT_ID is not set.
"""
global _default_service
if _default_service is None:
_default_service = GoogleAuthService()
return _default_service
def verify_google_token(id_token: str) -> GoogleUserInfo:
"""
Convenience function to verify a token using the default service.
Args:
id_token: The Google ID token to verify.
Returns:
GoogleUserInfo: Verified user information.
Raises:
InvalidTokenError: If verification fails.
ConfigurationError: If service is not configured.
"""
return get_google_auth_service().verify_token(id_token)
|