OAuth2-Google-Authentication-MySelf
OAuth2-Google-Authentication-With-Self-Callback

!pip install requests requests-oauthlib
Client Secret
- This is
client secretinformation 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
InstalledAppFlowofGoogle
# 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
| Aspect | Manual code (AuthHttpServer) | InstalledAppFlow |
|---|---|---|
| OAuth flow handled | manually build the auth URL, start a local server, capture auth_code, and exchange it for tokens | Fully handled by the library (InstalledAppFlow) |
| PKCE handling | explicitly generate code_verifier and code_challenge | Automatically handled by the library |
| HTTP requests | need to use requests.post to get tokens | Library internally requests the token and creates a Credentials object |
| Local server | manually run a server with http.server | flow.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.