OAuth 2.0 Client Credentials Grant Flow for Server-to-Server Authentication (Python)
Owner: SnippetBot
Created: 2026-09-08 00:00:54
Size: 6.30 KB
Expires: Never
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
import requests
import os
import time
from datetime import datetime, timedelta
class OAuth2Client:
def __init__(self, token_url, client_id, client_secret, scope=None):
self.token_url = token_url
self.client_id = client_id
self.client_secret = client_secret
self.scope = scope
self.access_token = None
self.expires_at = None # datetime object when token expires
def _get_new_token(self):
print("Requesting new OAuth2 access token...")
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
if self.scope:
data["scope"] = self.scope
try:
response = requests.post(self.token_url, headers=headers, data=data)
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
token_data = response.json()
self.access_token = token_data.get("access_token")
expires_in = token_data.get("expires_in") # seconds until expiration
if self.access_token and expires_in:
# Set expiration time a bit before actual expiration for safety (e.g., 60 seconds)
self.expires_at = datetime.now() + timedelta(seconds=expires_in - 60)
print(f"Successfully obtained new token. Expires at: {self.expires_at}")
else:
raise ValueError("Access token or expiration time missing from response.")
except requests.exceptions.RequestException as e:
print(f"Error obtaining OAuth2 token: {e}")
self.access_token = None
self.expires_at = None
raise
def get_access_token(self):
# Check if token is still valid or needs refreshing
if not self.access_token or (self.expires_at and datetime.now() >= self.expires_at):
self._get_new_token()
return self.access_token
def make_api_request(self, api_url, method="GET", **kwargs):
token = self.get_access_token()
if not token:
raise Exception("Could not obtain access token.")
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {token}"
try:
response = requests.request(method, api_url, headers=headers, **kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401: # Token might be invalid, try to refresh and retry
print("API request failed with 401. Attempting to refresh token and retry...")
self.access_token = None # Invalidate current token
token = self.get_access_token() # Get new token
if not token:
raise Exception("Could not refresh token after 401.")
headers["Authorization"] = f"Bearer {token}"
response = requests.request(method, api_url, headers=headers, **kwargs) # Retry request
response.raise_for_status()
return response.json()
raise # Re-raise other HTTP errors
except requests.exceptions.RequestException as e:
print(f"API request failed: {e}")
raise
# --- Usage Example ---
if __name__ == "__main__":
# --- Configuration (ideally from environment variables) ---
TOKEN_URL = os.getenv("OAUTH_TOKEN_URL", "https://your-oauth-server.com/oauth/token")
CLIENT_ID = os.getenv("OAUTH_CLIENT_ID", "your_client_id")
CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET", "your_client_secret")
API_BASE_URL = os.getenv("API_BASE_URL", "https://your-resource-server.com/api/v1")
API_SCOPE = "read write" # Optional scope
if "your_client_id" in CLIENT_ID or "your-oauth-server" in TOKEN_URL:
print("WARNING: Please configure OAUTH_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and API_BASE_URL environment variables or update values in script.")
# Exit or use dummy values for demonstration if not configured
TOKEN_URL = "https://example.com/oauth/token" # Placeholder
CLIENT_ID = "test_client" # Placeholder
CLIENT_SECRET = "test_secret" # Placeholder
API_BASE_URL = "https://example.com/api/v1" # Placeholder
client = OAuth2Client(TOKEN_URL, CLIENT_ID, CLIENT_SECRET, scope=API_SCOPE)
try:
# First API call - token will be obtained
print("
--- Making first API call ---")
data = client.make_api_request(f"{API_BASE_URL}/data")
print(f"API Response (first call): {data}")
# Simulate token expiration (for testing, do not do this in production)
# client.expires_at = datetime.now() - timedelta(seconds=10)
# Second API call - if token is still valid, it will be reused
print("
--- Making second API call (should reuse token) ---")
data = client.make_api_request(f"{API_BASE_URL}/another-endpoint")
print(f"API Response (second call): {data}")
# Wait until just before theoretical token expiration to force refresh
print("
--- Waiting to force token refresh ---")
# In a real scenario, client.expires_at would be set correctly.
# Here, we'll simulate waiting by making expires_at expire soon
if client.expires_at:
wait_time = (client.expires_at - datetime.now()).total_seconds() + 5 # Wait 5 seconds past expiration
if wait_time > 0:
print(f"Waiting for {int(wait_time)} seconds to force token refresh...")
time.sleep(wait_time)
else:
print("Token already expired or about to expire, will refresh on next call.")
else:
print("Cannot determine token expiration, assuming it will be refreshed if needed.")
time.sleep(2) # Give it some time if expires_at was not set
# Third API call - token should be refreshed
print("
--- Making third API call (should trigger token refresh) ---")
data = client.make_api_request(f"{API_BASE_URL}/status")
print(f"API Response (third call, after refresh): {data}")
except Exception as e:
print(f"An error occurred during API interaction: {e}")