Skip to main content

How to Make HTTP Requests with Bearer Token Authentication in Python

Many modern APIs use Bearer Token authentication (often JWTs - JSON Web Tokens) to authorize requests. This involves sending an Authorization header with the value Bearer <your_token>. Python's popular requests library and the built-in urllib module both provide ways to include this header in your HTTP requests.

This guide demonstrates how to send Bearer tokens correctly using requests (directly and via a custom auth class) and urllib.request.

Understanding Bearer Token Authentication

Bearer authentication is an HTTP authentication scheme involving security tokens called bearer tokens. The client sends this token in the Authorization header of the HTTP request to access protected resources. The server receiving the request verifies the token to grant access. The standard format for the header is:

Authorization: Bearer <token>

Where <token> is the actual token string (e.g., a long JWT).

The requests library (install via pip install requests) provides the most straightforward way to add custom headers, including the Authorization header.

Setting the Authorization Header

You create a Python dictionary representing the headers and pass it to the headers parameter of the request function (requests.get, requests.post, etc.).

Example: POST Request with Bearer Token

import requests
import json # Often needed for request/response body

# --- Configuration ---
API_ENDPOINT = 'https://jsonplaceholder.typicode.com/posts' # Example endpoint

# Replace with your actual Bearer token
YOUR_BEARER_TOKEN = 'YOUR_ACTUAL_SECRET_TOKEN_HERE'

# Data to send (for POST request)
post_data = {'title': 'foo', 'body': 'bar', 'userId': 1}

# --- Create Headers ---
# ✅ Define the headers dictionary with the Authorization header
request_headers = {
'Authorization': f'Bearer {YOUR_BEARER_TOKEN}',
'Content-Type': 'application/json' # Often needed for POST data
}

# --- Make the Request ---
try:
print(f"Sending POST request to {API_ENDPOINT}...")
response = requests.post(
API_ENDPOINT,
headers=request_headers,
json=post_data, # Use json parameter for dict data -> auto-sets Content-Type
# data=json.dumps(post_data), # Alternative if sending raw JSON string
timeout=10 # Good practice to set a timeout
)
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)

# --- Process Response ---
print(f"Status Code: {response.status_code}") # e.g., 201 Created

# Decode the JSON response body
response_data = response.json()
print("Response JSON:")
print(response_data)

except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")

Example Output (if token was valid and endpoint worked):

Sending POST request to https://jsonplaceholder.typicode.com/posts...
Status Code: 201
Response JSON:
{'title': 'foo', 'body': 'bar', 'userId': 1, 'id': 101}
  • We create the request_headers dictionary.
  • The 'Authorization' key's value starts with Bearer followed by the token.
  • This dictionary is passed via the headers= argument to requests.post().
  • The same headers dictionary can be used with requests.get(), requests.put(), etc.

Formatting the Header Value

You can construct the Authorization header string in several ways:

YOUR_BEARER_TOKEN = 'YOUR_ACTUAL_SECRET_TOKEN_HERE'

# Using f-string (Recommended)
header_fstring = f'Bearer {YOUR_BEARER_TOKEN}'
print(f"f-string: '{header_fstring}'")

# Using string concatenation (+)
header_concat = 'Bearer ' + YOUR_BEARER_TOKEN
print(f"Concatenation: '{header_concat}'")

# Using str.format()
header_format = 'Bearer {}'.format(YOUR_BEARER_TOKEN)
print(f"str.format(): '{header_format}'")

# All produce: 'Bearer YOUR_ACTUAL_SECRET_TOKEN_HERE'

f-strings are generally the most readable option in modern Python.

Method 2: Using requests with a Custom AuthBase Class

For reusable authentication logic, especially if handling token refresh or complex schemes, requests allows you to create custom authentication classes inheriting from requests.auth.AuthBase.

Creating a BearerAuth Class

import requests

class BearerAuth(requests.auth.AuthBase):
"""Custom Auth class for Bearer Token."""
def __init__(self, token):
self.token = token

# This method is called by requests to modify the request object
def __call__(self, request_obj):
# ✅ Add the Authorization header to the request
request_obj.headers['Authorization'] = f'Bearer {self.token}'
return request_obj # Return the modified request

This class takes the token during initialization and adds the correct header when attached to a request via the auth parameter.

Example: Using the Custom Class for GET/POST

# --- Reusing the BearerAuth class from above ---
import requests

# --- Configuration ---
API_ENDPOINT_USERS = 'https://jsonplaceholder.typicode.com/users/1' # GET example
API_ENDPOINT_POSTS = 'https://jsonplaceholder.typicode.com/posts' # POST example
YOUR_BEARER_TOKEN = 'YOUR_ACTUAL_SECRET_TOKEN_HERE'
post_data = {'title': 'new post', 'body': 'content', 'userId': 5}

# --- Create Auth Instance ---
bearer_auth = BearerAuth(YOUR_BEARER_TOKEN)

# --- Make GET Request ---
try:
print(f"\nSending GET request to {API_ENDPOINT_USERS}...")
# ✅ Pass the auth instance to the 'auth' parameter
response_get = requests.get(API_ENDPOINT_USERS, auth=bearer_auth, timeout=10)
response_get.raise_for_status()
print(f"GET Status Code: {response_get.status_code}")
# print(f"GET Response: {response_get.json()}")
except requests.exceptions.RequestException as e:
print(f"GET Error: {e}")


# --- Make POST Request ---
try:
print(f"\nSending POST request to {API_ENDPOINT_POSTS}...")
# ✅ Pass the same auth instance to the 'auth' parameter
response_post = requests.post(API_ENDPOINT_POSTS, json=post_data, auth=bearer_auth, timeout=10)
response_post.raise_for_status()
print(f"POST Status Code: {response_post.status_code}")
print(f"POST Response: {response_post.json()}")
except requests.exceptions.RequestException as e:
print(f"POST Error: {e}")

Using the auth parameter keeps the header logic separate from the main request call, which can be cleaner for complex authentication.

Method 3: Using urllib.request

Python's built-in urllib module can also send custom headers, though its API is generally considered less user-friendly than requests.

Adding Headers with urllib.request.Request

You need to create a Request object and add headers to it before opening the URL.

Example: POST Request with urllib

import urllib.request
import json # Needed to encode data and potentially decode response

# --- Configuration ---
API_ENDPOINT = 'https://jsonplaceholder.typicode.com/posts'
YOUR_BEARER_TOKEN = 'YOUR_ACTUAL_SECRET_TOKEN_HERE'
post_data_dict = {'title': 'urllib post', 'body': 'test', 'userId': 2}

# --- Prepare Data and Headers ---
# ✅ Encode the POST data to bytes
encoded_data = json.dumps(post_data_dict).encode('utf-8')
# ✅ Create headers dictionary
request_headers = {
'Authorization': f'Bearer {YOUR_BEARER_TOKEN}',
'Content-Type': 'application/json; charset=utf-8' # Be explicit
}

# --- Create and Send Request ---
# ✅ Create a Request object with url, data, and headers
req = urllib.request.Request(API_ENDPOINT, data=encoded_data, headers=request_headers, method='POST')

print(f"\nSending POST request to {API_ENDPOINT} using urllib...")
try:
with urllib.request.urlopen(req, timeout=10) as response:
# --- Process Response ---
print(f"Status Code: {response.getcode()}")
# Read the response body (bytes) and decode it
response_body_bytes = response.read()
response_body_str = response_body_bytes.decode('utf-8')
# Parse JSON if applicable
response_data = json.loads(response_body_str)
print("Response JSON (urllib):")
print(response_data)
except urllib.error.URLError as e:
print(f"urllib Error: {e}")
except json.JSONDecodeError as e:
print(f"JSON Decode Error: {e}")

Note the need to manually encode the request data to bytes and decode the response bytes.

Example: GET Request with urllib

import urllib.request
import json

# --- Configuration ---
API_ENDPOINT = 'https://jsonplaceholder.typicode.com/users/1'
YOUR_BEARER_TOKEN = 'YOUR_ACTUAL_SECRET_TOKEN_HERE'

# --- Prepare Headers ---
request_headers = {
'Authorization': f'Bearer {YOUR_BEARER_TOKEN}',
}

# --- Create and Send Request ---
# ✅ Create Request object, data is None for GET
req = urllib.request.Request(API_ENDPOINT, data=None, headers=request_headers, method='GET')

print(f"\nSending GET request to {API_ENDPOINT} using urllib...")
try:
with urllib.request.urlopen(req, timeout=10) as response:
# --- Process Response ---
print(f"Status Code: {response.getcode()}")
response_body_bytes = response.read()
response_body_str = response_body_bytes.decode('utf-8')
response_data = json.loads(response_body_str)
print("Response JSON (urllib):")
print(response_data)
except urllib.error.URLError as e:
print(f"urllib Error: {e}")
except json.JSONDecodeError as e:
print(f"JSON Decode Error: {e}")

Choosing the Right Method

  • requests with headers dict: Highly recommended for most use cases. It's simple, direct, and aligns well with how HTTP headers work.
  • requests with Custom AuthBase: Good for complex authentication logic, reusing authentication across multiple requests, or integrating with token refresh mechanisms. Might be overkill for simple bearer tokens.
  • urllib.request: Use if you cannot have external dependencies (like requests). Be prepared for more manual work involving data encoding/decoding and potentially more complex error handling.

Conclusion

Sending Bearer Token authentication with Python HTTP requests involves setting the Authorization header correctly.

  • The requests library makes this easy via the headers dictionary argument: headers={'Authorization': f'Bearer {token}'}. This is the preferred method for simplicity and readability.
  • requests also supports custom authentication classes (auth=BearerAuth(token)) for more structured or reusable authentication logic.
  • The built-in urllib.request module requires manually creating a Request object and adding the header to its headers dictionary, along with manual handling of request/response body encoding.

Choose the method that best suits your project's dependencies and complexity requirements. For most applications, the requests library offers a superior developer experience.