Henry Hoang

OAuth2-Google-Authentication-MySelf

OAuth2-Google-Authentication-MySelf

OAuth2-Google-Authentication-With-Self-Callback

!pip install requests requests-oauthlib

Client Secret

  • This is client secret information of Google Credential which genrated from Google Project
import json


with open("client_secret.json") as f:
    data = json.load(f)

# type(data)
"installed" in data and "client_id" in data['installed'] and "client_secret" in data["installed"]

Try to implement Local Server

  • Play role as InstalledAppFlow of Google
# Author: HoHai
# All rights reserved.

import webbrowser
import http.server
import socketserver
import threading
from urllib.parse import urlparse, parse_qs
import urllib

import base64
import os
import json
import hashlib

import requests

class Handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):

        query = urlparse(self.path).query
        params = parse_qs(query)

        # print(f"params receiving: {params}")

        if "code" in params:
            self.server.shared_data['auth_code'] = params["code"][0]
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"Authorization received. You can close this tab.")
            self.server.event.set()  # signal main thread
        else:
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"No authorization code found.")



class AuthHttpServer(socketserver.TCPServer):

    AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
    TOKEN_URL = "https://oauth2.googleapis.com/token"

    def __init__(self, server_address, client_secret_filepath):
        super().__init__(server_address, Handler)
        assert os.path.exists(client_secret_filepath), f"Please make sure the `client_secret_filepath` exists"

        with open(client_secret_filepath, "r") as f:
            data = json.load(f)

        assert "installed" in data \
                and "client_id" in data['installed'] \
                and "client_secret" in data["installed"], \
            "Please make sure this is valid client secret json file"

        self.client_id = data["installed"]["client_id"]
        self.client_secret = data["installed"]["client_secret"]


        self.shared_data = {}
        self.event = threading.Event()
        self.code_verifier, self.code_challenge = self.generate_pkce()

        # Build redirect URL
        self._redirect_uri = f"http://{server_address[0]}:{server_address[1]}/callback"
        print(self._redirect_uri)


    def generate_pkce(self):
        code_verifier = base64.urlsafe_b64encode(os.urandom(40)).rstrip(b'=').decode('utf-8')
        code_challenge = base64.urlsafe_b64encode(
            hashlib.sha256(code_verifier.encode('utf-8')).digest()
        ).rstrip(b'=').decode('utf-8')
        return code_verifier, code_challenge

    def start_server(self):
        socketserver.TCPServer.allow_reuse_address = True
        with self as server:
            server.handle_request()  # serve only one request

    def auth(self, extract_auth_code=True):
        # Prepare the authorization URL
        params = {
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": self._redirect_uri,
            "scope": "openid email profile",
            "code_challenge": self.code_challenge,
            "code_challenge_method": "S256",

            "access_type": "offline",
            "include_granted_scopes": "true",
        }
        auth_url = AuthHttpServer.AUTH_URL + "?" + urllib.parse.urlencode(params)
        print("Open this URL in your browser to authorize:")
        print(auth_url)

        # Open browser automatically
        webbrowser.open(auth_url, new=1)

        # Start local HTTP server in separate thread
        # shared_data = {}
        # event = threading.Event()
        # thread = threading.Thread(target=self.start_server, args=(self,))
        thread = threading.Thread(target=self.start_server)
        thread.start()

        # Wait until auth_code is received
        self.event.wait()

        # Then now we have `auth_code`
        if extract_auth_code:
            return requests.post(AuthHttpServer.TOKEN_URL, data={
                                    "code": self.shared_data['auth_code'],
                                    "client_id": self.client_id,
                                    "code_verifier": self.code_verifier,
                                    "client_secret": self.client_secret,
                                    "redirect_uri": self._redirect_uri,
                                    "grant_type": "authorization_code",
                                }).json()


        return self.shared_data['auth_code']




auth_server = AuthHttpServer(("localhost", 8765), client_secret_filepath="client_secret.json")
code = auth_server.auth()

print(code)

http://localhost:8765/callback
Open this URL in your browser to authorize:
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=661652986182-leoh8069397oikhodkdil8r5gumklq08.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8765%2Fcallback&scope=openid+email+profile&code_challenge=CzWAD4BgClK4DHCFacMfbCJo8ZMnjzY5-5klvUKm_Q0&code_challenge_method=S256&access_type=offline&include_granted_scopes=true
Alo toi o day: <socket.socket fd=91, family=2, type=1, proto=0, laddr=('127.0.0.1', 8765), raddr=('127.0.0.1', 54142)>


127.0.0.1 - - [04/Dec/2025 14:33:32] "GET /callback?code=4%2F0Ab32j91wxGc54M_EM4lkM9ZqdMsayAH-zmIm9vrwbgg75OmbnZipq6gfEmjohSyuKOQP9A&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=1&prompt=consent HTTP/1.1" 200 -


{'access_token': 'ya29.a0ATi6K2ttKUqcMx_MSQ7hN_J9tLD-bhEKInKLRNofcJva4lARQRekGvWftYM8tcXxG3ZTaXrkw4RVfJ67MOIGkRRtv8ej5Ot7EMQXjsKs-s8OerxrPJ4sZ2Mi-PEgXKCTCo260mdhAJgXb2wDjKD8iHP7cupAbSbt-Vvrdko-_O2q0zx_bDxThiW924BqPDxaVLGrvbUaCgYKAdkSARMSFQHGX2MiIAyfavD90H9GKdnOfeIEgQ0206', 'expires_in': 3599, 'refresh_token': '1//0eqN6gDVPE1G-CgYIARAAGA4SNwF-L9IrdaN3ebkUmHRPF_Dpw1S3WToo7Gs0AczYuKXvKcD5OB9SGcKUBrvcWBVWTMa685axy2c', 'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid', 'token_type': 'Bearer', 'id_token': 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ1NDNlMjFhMDI3M2VmYzY2YTQ3NTAwMDI0NDFjYjIxNTFjYjIzNWYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI2NjE2NTI5ODYxODItbGVvaDgwNjkzOTdvaWtob2RrZGlsOHI1Z3Vta2xxMDguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI2NjE2NTI5ODYxODItbGVvaDgwNjkzOTdvaWtob2RrZGlsOHI1Z3Vta2xxMDguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDg2NzQ0ODQwNjg4OTU0OTAwOTkiLCJlbWFpbCI6ImhvdHJvbmdoYWkyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiX0NhNDJuUl9lMjdUWmJSMGUwNmVTZyIsIm5hbWUiOiJIYWkgSG8gVHJvbmciLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTFlMWVVfa2JuVk5ReUpGbjEyOWt6SVd3TFpYVHNQTzgxeEg0VVVMalJHSWVnY3V3PXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkhhaSIsImZhbWlseV9uYW1lIjoiSG8gVHJvbmciLCJpYXQiOjE3NjQ4MzM2MTIsImV4cCI6MTc2NDgzNzIxMn0.sNPbSAH-4sfzJ2DCjhE7NaGdZrb8Ypm96VKLJoBe_9hiCu0X7BwmrgJxfDKKTy9ZRjTW27YF8V4pcvsGfOd5kY1PgfEjpFM8g3CAv64iv7esNu9rVlkEaLp1DGzil-yyAWXacZs6NzbCvFiibo2C3Cu-Rxu2drndWPgZwA5iiDd8DhMP_vEvbJm_Tji_6sVogsuGE65dgf94NDF9tKaPbFsrzYQuhnCA1RcCb4KPpvBC7DgzXKLPkQ4u0nqu9BOyvI687Fwi1McqSdWYckOinPD6S1SWjpZgpCbSmaFeSru4GvrB-9H87xj1yhGd2QGmoC9KFbA7-BhoLgv8SpmB5g'}
code

Comparation

AspectManual code (AuthHttpServer)InstalledAppFlow
OAuth flow handledmanually build the auth URL, start a local server, capture auth_code, and exchange it for tokensFully handled by the library (InstalledAppFlow)
PKCE handlingexplicitly generate code_verifier and code_challengeAutomatically handled by the library
HTTP requestsneed to use requests.post to get tokensLibrary internally requests the token and creates a Credentials object
Local servermanually run a server with http.serverflow.run_local_server() spins up a temporary local server and handles the callback automatically

Summary: InstalledAppFlow saves a lot of boilerplate. don’t manually generate PKCE or parse query strings.

On this page