What is OAuth 2.0 and why is it necessary?

OAuth 2.0 is a widely-used method for allowing third-party apps to access user data without needing their passwords. It’s commonly used for apps that need to connect to services like Google Drive. Instead of asking for passwords, OAuth 2.0 uses tokens to securely grant permission. This prevents apps from needing to store sensitive login details. As a result, it offers a safer and standardized way for users to authorize apps without exposing their credentials.

This blog covers how to implement OAuth 2.0 with Google Drive using Python and FastAPI. It walks through the entire authentication process, from setting up credentials to listing and downloading files from Google Drive. The provided code offers a practical example of connecting a FastAPI app to Google Drive using OAuth.

Setting Up The Project
Step 1: Install Required Libraries

First, install the necessary Python packages for handling OAuth flows and interacting with Google APIs:

    pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client fastapi uvicorn

    Step 2: Setting up OAuth Credentials in Google Cloud Console
    1. Create a Project
    2. Enable Google Drive API

    Navigate to APIs & Services > Library, search for “Google Drive API,” and enable it.

    3. Set Up OAuth Consent Screen
    • Go to APIs & Services > OAuth consent screen.
    • Select User Type: Choose External. (This option allows anyone with a Google account to use the app.)
    • App Information: Enter the required details such as App Name, User Support Email, and Developer Contact Information.
    • Scopes: Click ‘Add or remove scopes’ and include the required scopes. For this tutorial, the scope needed is:
      https://www.googleapis.com/auth/drive.readonly
    • Test Users : Specify the users who are permitted to authenticate and use the app during testing.

      Note: A maximum of 100 test users can be added in development mode. To enable broader access, the app must be verified by Google.

    • Save and Continue.
        4. Create OAuth Credentials
        • Go to APIs & Services > Credentials, click Create Credentials, and choose OAuth Client ID.
        • Select Desktop App.
        • Download the client_secret.json file and save it in the project directory.

          This step completes the setup of OAuth credentials and consent screen, enabling users to authenticate and allow access to their Google Drive.

          Step 3: Code Implementation

          The implementation uses FastAPI to create a web service with three endpoints for Google Drive interaction:

          • /auth: Handles user authentication.
          • /drive/list: Lists files from Google Drive.
          • /drive/download/{file_id}: Downloads a file by its ID.

          Imports and FastAPI initialization (main.py):

          # Necessary imports
          import os
          import io
          from fastapi import FastAPI
          from google.oauth2.credentials import Credentials
          from google_auth_oauthlib.flow import InstalledAppFlow
          from google.auth.transport.requests import Request
          from googleapiclient.discovery import build
          from googleapiclient.http import MediaIoBaseDownload

          # Initialize FastAPI app
          app = FastAPI()

          # Google OAuth 2.0 scopes for Google Drive
          SCOPES = [“https://www.googleapis.com/auth/drive.readonly”]

          • SCOPES define the level of access the app requires (e.g., read-only access to Google Drive).
          File paths for OAuth token and client secrets

          # File paths for token storage
          TOKEN_FILE_PATH = “token.json”
          CLIENT_SECRET_FILE = “client_secret.json”

          These paths refer to where the token and client secret files will be stored. The client_secret.json file contains the OAuth credentials. The token.json stores the OAuth token, allowing the application to reuse it without requiring repeated logins.

          load_creds() Function
          • To avoid asking user to login each time, credentials are stored in a file and reused unless the token has expired. If the token expires, it can be refreshed using the refresh method.

          def load_creds():
            “””Load and refresh credentials if necessary.”””
            if os.path.exists(TOKEN_FILE_PATH):
                creds = Credentials.from_authorized_user_file(TOKEN_FILE_PATH, SCOPES)
                if creds and creds.expired and creds.refresh_token:
                    creds.refresh(Request())
                    with open(TOKEN_FILE_PATH, “w”) as token_file:
                        token_file.write(creds.to_json())
                return creds
            return None

          auth() Endpoint

          @app.get(“/auth”)
          async def auth():
            “””Perform authentication and return a success message.”””
            creds = load_creds()
            if not creds:
                flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
                creds = flow.run_local_server(port=0
                with open(TOKEN_FILE_PATH, “w”) as token_file:
                    token_file.write(creds.to_json())

            return {“message”: “Successfully logged in.”}

          The /auth endpoint begins the OAuth process. It uses the InstalledAppFlow class from google_auth_oauthlib to handle authentication. A browser window opens where the user logs into their Google account and grants permissions. After authorization, the app gets an access token, which is saved in token.json for future use.

          See the screenshots below for a visual overview of the authorization steps:

          * After selecting an account, the following window appears.

          * Choose “Continue”, and the following window will request permission to access the necessary resources.

          * Select the desired permissions to grant and click “Continue”.

          Once the user authorizes the app, the access token is automatically saved in the token.json file in the project directory. This file allows the app to reuse the credentials without asking the user to log in again. See the screenshot below:

          list_files() Endpoint
          • This endpoint lists the files in the user’s Google Drive. It uses the Google Drive API to fetch files and returns their id and name.

          @app.get(“/drive/list”)
          async def list_files():
            “””List all files from Google Drive.”””
            creds = load_creds()
            if not creds:
                return {“message”: “User not authenticated.”}

            service = build(“drive”, “v3”, credentials=creds)

            # List files with the required fields (id, name)
            results = service.files().list(fields=“files(id, name)”).execute()

            items = results.get(“files”, [])

            # Collect file information (id, name)
            files_with_info = [{“id”: item[“id”], “name”: item[“name”]} for item in items]

            return files_with_info

          • The build(“drive”, “v3”, credentials=creds) creates a connection to the Google Drive API, allowing the app to access and retrieve files from the user’s Drive with their authorized credentials.
          • The service.files().list() fetches and returns the file IDs and names from Google Drive.
          download_file() Endpoint
          • In this endpoint, files can be downloaded by providing their file ID. The downloaded file will be saved in the project folder.

          @app.get(“/drive/download/{file_id}”)
          async def download_file(file_id: str):
            “””Download a specific file from Google Drive.”””
            creds = load_creds()
            if not creds:
                return {“message”: “User not authenticated.”}

            service = build(“drive”, “v3”, credentials=creds)
            request = service.files().get_media(fileId=file_id)
            file_name = service.files().get(fileId=file_id).execute().get(“name”)

            fh = io.BytesIO()
            downloader = MediaIoBaseDownload(fh, request)
            done = False
            while not done:
                status, done = downloader.next_chunk()

            fh.seek(0)
            with open(file_name, “wb”) as file:
                file.write(fh.read())

            return {“message”: f”File ‘{file_name}’ downloaded successfully.”}

          • The build(“drive”, “v3”, credentials=creds) creates a connection to the Google Drive API.
          • A get_media request is made to fetch the file content based on the provided file_id.
          Step 4: Running the FastAPI Application

          Now that the credentials and endpoints are set up, run the FastAPI app by executing:
          uvicorn main:app –reload
          By default, the host is set to localhost, and the port is 8000. Visit http://localhost:8000/docs to access the Swagger UI, where testing the endpoints can be done directly.

          Conclusion

          This blog covers the OAuth 2.0 implementation for Google Drive using FastAPI. It illustrates the process of authenticating, listing files, and securely downloading files from Google Drive using token-based access.
          This example can be extended to interact with other Google APIs by updating the OAuth scopes. This serves as a good starting point for applications that need to connect with Google services.